Browse Source

Merge branch '7.x-3.x' into 553-tv3-importer_hooks

Stephen Ficklin 6 years ago
parent
commit
6baf1e035e
38 changed files with 833 additions and 395 deletions
  1. 1 1
      legacy/tripal_analysis/includes/tripal_analysis.chado_node.inc
  2. 1 1
      legacy/tripal_contact/includes/tripal_contact.chado_node.inc
  3. 1 1
      legacy/tripal_feature/includes/tripal_feature.chado_node.inc
  4. 1 1
      legacy/tripal_featuremap/includes/tripal_featuremap.chado_node.inc
  5. 1 1
      legacy/tripal_library/includes/tripal_library.chado_node.inc
  6. 1 1
      legacy/tripal_organism/includes/tripal_organism.chado_node.inc
  7. 1 1
      legacy/tripal_phylogeny/includes/tripal_phylogeny.chado_node.inc
  8. 1 1
      legacy/tripal_project/includes/tripal_project.chado_node.inc
  9. 1 1
      legacy/tripal_pub/includes/tripal_pub.chado_node.inc
  10. 1 1
      legacy/tripal_stock/includes/tripal_stock.chado_node.inc
  11. 4 2
      tripal/api/tripal.collections.api.inc
  12. 2 2
      tripal/api/tripal.quotas.api.inc
  13. 26 7
      tripal/includes/TripalBundleUIController.inc
  14. 2 1
      tripal/includes/TripalEntityCollection.inc
  15. 1 1
      tripal/includes/TripalFieldDownloaders/TripalCSVDownloader.inc
  16. 18 15
      tripal/includes/TripalFieldDownloaders/TripalFieldDownloader.inc
  17. 1 1
      tripal/includes/TripalFieldDownloaders/TripalTabDownloader.inc
  18. 6 0
      tripal/includes/TripalFieldQuery.inc
  19. 7 92
      tripal/includes/tripal.collections.inc
  20. 9 9
      tripal/includes/tripal.field_storage.inc
  21. 348 73
      tripal/includes/tripal.user.inc
  22. 56 0
      tripal/theme/css/tripal_user_files.css
  23. BIN
      tripal/theme/images/directory.png
  24. BIN
      tripal/theme/images/txt.png
  25. 77 0
      tripal/theme/js/tripal.user_files.js
  26. 8 18
      tripal/tripal.module
  27. 1 1
      tripal/tripal_views_query.inc
  28. 6 2
      tripal/views_handlers/tripal_views_handler_area_collections.inc
  29. 7 0
      tripal_chado/api/tripal_chado.mviews.api.inc
  30. 47 0
      tripal_chado/api/tripal_chado.semweb.api.inc
  31. 1 1
      tripal_chado/includes/TripalFields/local__contact/local__contact_widget.inc
  32. 5 1
      tripal_chado/includes/TripalFields/obi__organism/obi__organism.inc
  33. 1 1
      tripal_chado/includes/TripalFields/obi__organism/obi__organism_formatter.inc
  34. 8 1
      tripal_chado/includes/TripalFields/obi__organism/obi__organism_widget.inc
  35. 1 1
      tripal_chado/includes/TripalFields/sep__protocol/sep__protocol_widget.inc
  36. 179 150
      tripal_chado/includes/tripal_chado.fields.inc
  37. 2 2
      tripal_ws/includes/TripalWebService/TripalContentService_v0_1.inc
  38. 0 4
      tripal_ws/includes/TripalWebServiceResource.inc

+ 1 - 1
legacy/tripal_analysis/includes/tripal_analysis.chado_node.inc

@@ -17,7 +17,7 @@
 function tripal_analysis_node_info() {
   $nodes = array();
   $nodes['chado_analysis'] = array(
-    'name'        => t('Analysis'),
+    'name'        => t('Analysis (Tripal v2 legacy)'),
     'base'        => 'chado_analysis',
     'description' => t('An analysis'),
     'has_title'   => TRUE,

+ 1 - 1
legacy/tripal_contact/includes/tripal_contact.chado_node.inc

@@ -16,7 +16,7 @@ function tripal_contact_node_info() {
 
   return array(
     'chado_contact' => array(
-      'name'        => t('Contact'),
+      'name'        => t('Contact (Tripal v2 legacy)'),
       'base'        => 'chado_contact',
       'description' => t('A contact from the Chado database'),
       'has_title'   => TRUE,

+ 1 - 1
legacy/tripal_feature/includes/tripal_feature.chado_node.inc

@@ -16,7 +16,7 @@ function tripal_feature_node_info() {
   $nodes = array();
 
   $nodes['chado_feature'] = array(
-    'name'        => t('Feature'),
+    'name'        => t('Feature (Tripal v2 legacy)'),
     'base'        => 'chado_feature',
     'description' => t('A feature from the chado database'),
     'has_title'   => TRUE,

+ 1 - 1
legacy/tripal_featuremap/includes/tripal_featuremap.chado_node.inc

@@ -15,7 +15,7 @@
 function tripal_featuremap_node_info() {
   $nodes = array();
   $nodes['chado_featuremap'] = array(
-    'name'        => t('Feature Map'),
+    'name'        => t('Feature Map (Tripal v2 legacy)'),
     'base'        => 'chado_featuremap',
     'description' => t('A map of features from the chado database (e.g. genetic map)'),
     'has_title'   => TRUE,

+ 1 - 1
legacy/tripal_library/includes/tripal_library.chado_node.inc

@@ -15,7 +15,7 @@
 function tripal_library_node_info() {
   $nodes = array();
   $nodes['chado_library'] = array(
-    'name'        => t('Library'),
+    'name'        => t('Library (Tripal v2 legacy)'),
     'base'        => 'chado_library',
     'description' => t('A library from the chado database'),
     'has_title'   => TRUE,

+ 1 - 1
legacy/tripal_organism/includes/tripal_organism.chado_node.inc

@@ -15,7 +15,7 @@
 function tripal_organism_node_info() {
   $nodes = array();
   $nodes['chado_organism'] = array(
-    'name'        => t('Organism'),
+    'name'        => t('Organism (Tripal v2 legacy)'),
     'base'        => 'chado_organism',
     'description' => t('An organism'),
     'has_title'   => TRUE,

+ 1 - 1
legacy/tripal_phylogeny/includes/tripal_phylogeny.chado_node.inc

@@ -16,7 +16,7 @@
 function tripal_phylogeny_node_info() {
   $nodes = array();
   $nodes['chado_phylotree'] = array(
-    'name'        => t('Phylotree'),
+    'name'        => t('Phylotree (Tripal v2 legacy)'),
     'base'        => 'chado_phylotree',
     'description' => t('A phylotree from the chado database'),
     'has_title'   => TRUE,

+ 1 - 1
legacy/tripal_project/includes/tripal_project.chado_node.inc

@@ -16,7 +16,7 @@
 function tripal_project_node_info() {
   return array(
     'chado_project' => array(
-      'name'        => t('Project'),
+      'name'        => t('Project (Tripal v2 legacy)'),
       'base'        => 'chado_project',
       'description' => t('A project from the Chado database'),
       'has_title'   => TRUE,

+ 1 - 1
legacy/tripal_pub/includes/tripal_pub.chado_node.inc

@@ -17,7 +17,7 @@
 function tripal_pub_node_info() {
   $nodes = array();
   $nodes['chado_pub'] = array(
-    'name'        => t('Publication'),
+    'name'        => t('Publication (Tripal v2 legacy)'),
     'base'        => 'chado_pub',
     'description' => t('A publication from the Chado database'),
     'has_title'   => TRUE,

+ 1 - 1
legacy/tripal_stock/includes/tripal_stock.chado_node.inc

@@ -15,7 +15,7 @@
 function tripal_stock_node_info() {
   return array(
     'chado_stock' => array(
-      'name'        => t('Stock'),
+      'name'        => t('Stock (Tripal v2 legacy)'),
       'base'        => 'chado_stock',
       'description' => t('A Chado Stock is a collection of material that can be sampled and have experiments performed on it.'),
       'has_title'   => TRUE,

+ 4 - 2
tripal/api/tripal.collections.api.inc

@@ -54,11 +54,13 @@ function tripal_create_collection($details) {
     $collection_id = $collection->getCollectionID();
     $collection->addBundle($details);
 
-    drupal_set_message(t("Collection '%name' created with %num_recs record(s). Check the !view to generate file links.",
+    
+    
+    drupal_set_message(t("Collection '%name' created with %num_recs record(s). Files for this collection are not yet created.  To generate files, check the !view.",
       array(
         '%name' => $details['collection_name'],
         '%num_recs' => count($details['ids']),
-        '!view' => l('data collections page', 'user/' . $user->uid . '/data-collections'),
+        '!view' => l('files page', 'user/' . $user->uid . '/files'),
       ))
     );
     return $collection;

+ 2 - 2
tripal/api/tripal.quotas.api.inc

@@ -114,7 +114,7 @@ function tripal_expire_files(TripalJob $job = NULL) {
  *   The file ID of the file to reset.
  *   
  * @return
- *   TRUE on success, FALSE on failure.
+ *   The new expiration date on success, FALSE on failure.
  */
 function tripal_reset_file_expiration($fid) {
   
@@ -138,5 +138,5 @@ function tripal_reset_file_expiration($fid) {
     tripal_report_error('trp_quota', TRIPAL_ERROR, $e->getMessage());
     return FALSE;
   }
-  return TRUE;
+  return $expiration_date;
 }

+ 26 - 7
tripal/includes/TripalBundleUIController.inc

@@ -878,9 +878,30 @@ function tripal_admin_access($entity) {
     return FALSE;
   }
 
-  // Identify the administrative user roles.
-  $admin_role = user_role_load_by_name('administrator');
-  $roles = array($admin_role->rid => $admin_role->name);
+  // Get the administrative user roles.
+  $admin_role = NULL;
+  $admin_rid = variable_get('user_admin_role'); 
+  if (!$admin_rid) {
+    // If we couldn't identify a single role from the 'user_admin_role' variable
+    // then let's get the role that is currently set to administer tripal. If
+    // there is more than one then we don't really know which to choose unless
+    // the default rid of '3' is present.
+    $admin_roles = user_roles(FALSE, 'administer tripal');
+    if (count(array_keys($admin_roles)) == 1) {
+      $admin_rid = key($admin_roles);
+    }
+    // The rid 3 is Drupal's default for the admin user.
+    else if (in_array(3, array_keys($admin_roles))) {
+      $admin_rid = 3;
+    }
+  }
+  
+  // If we can't find a unique admin role then just don't add one and
+  // the user will be forced to manually set permissions for the admin.
+  if (!$admin_rid) {
+    return FALSE;
+  }
+  
   // Define the permissions.
   $permission_for_role = array(
     'create ' . $bundle->name => TRUE,
@@ -888,11 +909,9 @@ function tripal_admin_access($entity) {
     'edit ' . $bundle->name => TRUE,
     'delete ' . $bundle->name => TRUE,
   );
-
+  
   // Assign the permissions
-  foreach($roles as $role => $value){
-    user_role_change_permissions($role, $permission_for_role);
-  }
+  user_role_change_permissions($admin_rid, $permission_for_role);
 
   return TRUE;
 }

+ 2 - 1
tripal/includes/TripalEntityCollection.inc

@@ -463,9 +463,10 @@ class TripalEntityCollection {
     }
 
     $outfile = $this->getOutfile($formatter);
+    
     // Make sure the user directory exists
     $user_dir = 'public://tripal/users/' . $this->user->uid;
-    $outfilePath = $user_dir. '/' . $outfile;
+    $outfilePath = $user_dir. '/data_collections/' . $outfile;
     return $outfilePath;
   }
 

+ 1 - 1
tripal/includes/TripalFieldDownloaders/TripalCSVDownloader.inc

@@ -54,7 +54,7 @@ class TripalCSVDownloader extends TripalFieldDownloader {
       $field_name = $field['field_name'];
 
       // If we only have one item for this value then add it.
-      if (count($entity->{$field_name}['und']) == 1) {
+      if (is_array($entity->{$field_name}['und']) and 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.

+ 18 - 15
tripal/includes/TripalFieldDownloaders/TripalFieldDownloader.inc

@@ -143,11 +143,22 @@ abstract class TripalFieldDownloader {
 
     // Make sure the user directory exists
     $user = user_load($this->collection->uid);
-    $user_dir = 'public://tripal/users/' . $user->uid;
+    $user_dir = tripal_get_user_files_dir($user);
+    if (!tripal_is_user_files_dir_writeable($user)) {
+      throw new Exception(t("The user's data directory is not writeable: !user_dir.", array('!user_dir' => $user_dir)));
+    }
 
     // Set the collection ID of the collection that this downloader will use.
     $this->collection_id = $collection_id;
-    $this->outfile = $user_dir . '/' . $outfile_name;
+    
+    // Make sure the data_collections directory exists.
+    $collections_dir = $user_dir . '/data_collections';
+    if (!file_exists($collections_dir)) {
+      if (!file_prepare_directory($collections_dir, [FILE_CREATE_DIRECTORY])) {
+        throw new Exception(t("The data_collection directory cannot be created: !collections_dir.", array('!collections_dir' => $collections_dir)));
+      }
+    }
+    $this->outfile = $collections_dir . '/' . $outfile_name;
 
     // A data collection may have multiple bundles.  We'll need to get
     // them all and store them.
@@ -161,11 +172,6 @@ abstract class TripalFieldDownloader {
       $this->collection_bundles[] = $collection_bundle;
     }
  
-    $user_dir = tripal_get_user_files_dir($user);
-    if (!tripal_is_user_files_dir_writeable($user)) {
-      throw new Exception(t("The user's data directory is not writeable: !user_dir.", array('!user_dir' => $user_dir)));
-    }
-
     // Map the fields to their term accessions.
     $this->setFields();
     $this->setFields2Terms();
@@ -294,15 +300,12 @@ abstract class TripalFieldDownloader {
       $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./
-    // But, only add the usage if it doens't already exists.
+    
+    // Associate this file with data collections if it isn't already
     $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);
-      }
+    if (!array_key_exists('tripal', $usage) or 
+        !array_key_exists('data_collection', $usage['tripal'])) {
+      file_usage_add($file, 'tripal', 'data_collection', $this->collection_id);
     }
   }
 

+ 1 - 1
tripal/includes/TripalFieldDownloaders/TripalTabDownloader.inc

@@ -54,7 +54,7 @@ class TripalTabDownloader extends TripalFieldDownloader {
        $field_name = $field['field_name'];
 
        // If we only have one item for this value then add it.
-       if (count($entity->{$field_name}['und']) == 1) {
+       if (is_array($entity->{$field_name}['und']) and 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.

+ 6 - 0
tripal/includes/TripalFieldQuery.inc

@@ -120,6 +120,12 @@ class TripalFieldQuery extends EntityFieldQuery {
       $results = $this->_intersectResults($results, $st_results);
     }
 
+    if ($this->count == TRUE) {
+      if (is_numeric($results)) {
+        return $results;
+      }
+      return count($results['TripalEntity']);
+    }
     return $results;
   }
   

+ 7 - 92
tripal/includes/tripal.collections.inc

@@ -1,85 +1,6 @@
 <?php
 
 
-/**
- *
- */
-function tripal_user_collections_page() {
-  global $user;
-
-  $headers = array('Name', 'Description', 'Create Date', 'Download Formats',
-    'Generate File', 'Actions');
-  $rows = array();
-
-  $collections = db_select('tripal_collection', 'tc')
-    ->fields('tc', array('collection_id'))
-    ->condition('uid', $user->uid)
-    ->orderBy('tc.collection_name')
-    ->execute();
-
-  while ($collection_id = $collections->fetchField()) {
-    $collection = new TripalEntityCollection();
-    $collection->load($collection_id);
-
-    $downloads = array();
-    $formatters = $collection->getFormatters();
-
-    foreach ($formatters as $class_name => $label) {
-
-      $outfile = $collection->getOutfilePath($class_name);
-
-      if (file_exists($outfile)) {
-        $outfileURL = file_create_url($outfile);
-        $downloads[] = l($label, $outfileURL);
-      }
-      else {
-        $downloads[] = 'Waiting on ' . $label . '...';
-      }
-    }
-    $download_list = theme_item_list(array(
-      'items' => $downloads,
-      'title' => '',
-      'type' => 'ul',
-      'attributes' => array(),
-    ));
-
-    $rows[] = array(
-      'data' => array(
-        $collection->getName(),
-        $collection->getDescription(),
-        $collection->getCreateDate(),
-        $download_list,
-        l('Generate File for Download', 'user/' . $user->uid . '/data-collections/generate/' . $collection_id),
-        l('View', 'user/' . $user->uid . '/data-collections/' . $collection_id . '/view') . ' ' .
-        l('Delete', 'user/' . $user->uid . '/data-collections/' . $collection_id . '/delete'),
-      ),
-    );
-  }
-
-  $content['instructions'] = array(
-    '#type' => 'markup',
-    '#markup' => '<p>' .  t('Data collections allow you to store sets of data
-       for download or later use by other tools on this site.  Typically data
-       collections are created using search tools.  The following data
-       collections are available to you.  Some files take time to generate
-       before they can be downloaded.') . '</p>',
-  );
-  $content['files_table'] = array(
-    '#type' => 'item',
-    '#title' => 'Data Collections',
-    '#markup' => theme_table(array(
-      'header' => $headers,
-      'rows' => $rows,
-      'attributes' => array(),
-      'caption' => '',
-      'colgroups' => array(),
-      'sticky' => TRUE,
-      'empty' => t('You currently have no data collections.')
-    )),
-  );
-
-  return $content;
-}
 
 /**
  * Renders a view of a data collection
@@ -91,7 +12,7 @@ function tripal_user_collections_view_page($uid, $collection_id) {
   $breadcrumb = array();
   $breadcrumb[] = l('Home', '<front>');
   $breadcrumb[] = l($user->name, 'user/' . $uid);
-  $breadcrumb[] = l('Data Collections', 'user/' . $uid . '/data-collections');
+  $breadcrumb[] = l('Files', 'user/' . $uid . '/files');
   drupal_set_breadcrumb($breadcrumb);
 
   $collection = new TripalEntityCollection();
@@ -161,13 +82,6 @@ function tripal_user_collections_view_page($uid, $collection_id) {
     '#markup' => number_format($num_entities),
   );
 
-  $contents['peek'] = array(
-    '#type' => 'item',
-    '#title' => 'Data View',
-    '#markup' => 'Coming soon...',
-  );
-
-
   return $contents;
 }
 /**
@@ -182,7 +96,7 @@ function tripal_user_collections_delete_form($form, &$form_state,  $uid, $collec
 
   $form = confirm_form($form,
       t('Click the delete button below to confirm deletion of the collection titled: %title',
-          array('%title' => $collection->getName())), 'user/' . $uid . '/data-collections',
+          array('%title' => $collection->getName())), 'user/' . $uid . '/files',
       '<p>' .t('This action cannot be undone.') .'</p>', t('Delete'), t('Cancel'), 'confirm');
 
   return $form;
@@ -206,10 +120,11 @@ function tripal_user_collections_delete_form_submit($form, &$form_state) {
     }
     catch (Exception $e) {
       drupal_set_message(t('There was a problem deleting the data collection ' .
-          'please contact the site to report the error: !message', array('!message', $e->getMessage())), 'error');
+        'please contact the site to report the error: !message', 
+        array('!message'=> $e->getMessage())), 'error');
     }
   }
-  drupal_goto('user/' . $user->uid . '/data-collections');
+  drupal_goto('user/' . $user->uid . '/files');
 }
 
 function tripal_user_collections_generate_file_form($form, &$form_state,  $uid, $collection_id) {
@@ -220,7 +135,7 @@ function tripal_user_collections_generate_file_form($form, &$form_state,  $uid,
   $collection->load($collection_id);
   $form = confirm_form($form,
       t('Confirm creation of files for the data collection named: "%title"',
-          array('%title' => $collection->getName())), 'user/' . $uid . '/data-collections',
+          array('%title' => $collection->getName())), 'user/' . $uid . '/files',
       '<p>' .t('It may take some time for the file(s) to be generated.  An email will be sent when files are ready.') .'</p>', t('Generate File'), t('Cancel'), 'confirm');
 
   return $form;
@@ -249,5 +164,5 @@ function tripal_user_collections_generate_file_form_submit($form, &$form_state)
     }
   }
 
-  drupal_goto('user/' . $user->uid . '/data-collections');
+  drupal_goto('user/' . $user->uid . '/files');
 }

+ 9 - 9
tripal/includes/tripal.field_storage.inc

@@ -78,7 +78,7 @@ function tripal_field_storage_query($query) {
   $select->join('tripal_bundle', 'TB', 'TE.bundle = TB.name');
   $select->fields('TE', array('id'));
   $select->fields('TB', array('name'));
-  
+
   // Apply any entity condition filters.
   if ($query->entityConditions) {
     if (array_key_exists('bundle', $query->entityConditions)) {
@@ -86,11 +86,11 @@ function tripal_field_storage_query($query) {
     }
   }
 
-  if ($query->relationshipConditions) {
+  if (property_exists($query, 'relationshipConditions') && $query->relationshipConditions) {
     foreach ($query->relationshipConditions as $table_alias => $reldetails) {
       $field = $reldetails['field'];
       $value = $reldetails['value'];
-      $op = $reldetails['op'];        
+      $op = $reldetails['op'];
       $relationship = $query->relationships[$table_alias];
       $table = $relationship['table'];
       $select->join($table, $table_alias, 'TE.id = ' . $table_alias . '.' . $relationship['field']);
@@ -101,7 +101,7 @@ function tripal_field_storage_query($query) {
   // Add in any filters to the query.
   foreach ($query->fieldConditions as $index => $condition) {
     $field = $condition['field'];
-    
+
     // Skip conditions that don't belong to this storage type.
     if ($field['storage']['type'] != 'tripal_no_storage') {
       continue;
@@ -129,17 +129,17 @@ function tripal_field_storage_query($query) {
       $select->orderBy('TB.label', $direction);
     }
   }
-  
+
   // Add a range of records to retrieve
   if ($query->range) {
     $select->range($query->range['start'], $query->range['length']);
   }
-  
+
   // Only include records that are deleted.  Tripal doesn't keep track of
   // records that are deleted that need purging separately so we can do nothing
   // with this.
   if (property_exists($query, 'deleted') and $query->deleted) {
-    // There won't ever be field data marked as deleted so just created a 
+    // There won't ever be field data marked as deleted so just created a
     // condition that always evaluates to false.
     $select->where('1=0');
   }
@@ -147,10 +147,10 @@ function tripal_field_storage_query($query) {
 //   dpm($query);
 //   dpm($select->__toString());
 //   dpm($select->getArguments());
-  
+
   // Perform the query and return the results.
   $entities = $select->execute();
-  
+
   $result = array(
     'TripalEntity' => array(),
   );

+ 348 - 73
tripal/includes/tripal.user.inc

@@ -1,53 +1,211 @@
 <?php
 
 
-/**
- * Provides the page with a list of files uploaded by the user.
- *
- * @param $uid
- *   The user ID.
- *
- * @return
- *   A Drupal render array.
- */
- function tripal_user_files_page($uid) {
-
-   // Get all of the files that have been uploaded by the user.
-   // TODO: we should make this a paged query in case a user has a huge
-   // numbef of uploaded files.
-   $sql = "
-     SELECT FM.fid, FM.filename, TGEF.expiration_date
+function tripal_user_get_files($uid){
+  $directory = [];
+  
+  // Get all of the files that have been uploaded by the user.
+  $sql = "
+     SELECT FM.fid, FM.filename, FM.uri, FM.uid, FM.filesize, TGEF.expiration_date
      FROM {file_managed} FM
        INNER JOIN {file_usage} FU on FM.fid = FU.fid and FM.uid = :user_id
        LEFT JOIN {tripal_expiration_files} TGEF on TGEF.fid = FU.fid
      WHERE FU.module = 'tripal'
      ORDER BY FM.filename
    ";
-   $files = db_query($sql, array(':user_id' => $uid));
-   $rows = array();
-   While ($entry = $files->fetchObject()) {
-    $file = file_load($entry->fid);
-
+  $files = db_query($sql, array(':user_id' => $uid));
+  $rows = array();
+  While ($file = $files->fetchObject()) {
+    
     // Don't list files that don't exist on the file system.
     if (!file_exists($file->uri)) {
       continue;
     }
+    
+    // Files have to be in the User's directory.
+    if (!preg_match('/^public:\/\/tripal\/users\//', $file->uri)) {
+      continue;
+    }
+    
+    // If the expiration date is somehow not set, then set it.
+    if (!$file->expiration_date) {
+      $file->expiration_date = tripal_reset_file_expiration($file->fid);
+    }
+    
+    // Organize the file into it's directory structure.
+    $rpath = preg_replace('/^public:\/\/tripal\/users\/'. $uid . '\//', '', $file->uri); 
+    $paths = explode('/', $rpath);
+    _tripal_user_build_files_dir($directory, $paths, $file); 
+  }
+  
+  return $directory;
+}
+
+/** 
+ * A recursive helper function for building the directory file tree.
+ * 
+ * @param $directory
+ *   The $directory array into which the file will be placed.
+ * @param $paths
+ *   An containing the directory path of the file. Each Directory
+ *   is a separate element of the array.
+ * @param $file
+ *   The file object to add to the $directory object.
+ */
+function _tripal_user_build_files_dir(&$directory, $paths, $file) {
+  $path = array_shift($paths);
+  if (count($paths) == 0) {
+    $directory[$path] = $file;
+  }
+  else {
+    _tripal_user_build_files_dir($directory[$path], $paths, $file);
+  }
+}
 
-    $date_uploaded = date('Y-m-d H:i:s', $file->timestamp);
-    $expiration = $entry->expiration_date ? date('Y-m-d H:i:s', $entry->expiration_date) : '';
-    $actions = l('Delete', "user/$uid/files/$file->fid/delete") . ' | ' .
-               l('Renew', "user/$uid/files/$file->fid/renew");
+/** 
+ * Generates an item list of the files.
+ * 
+ * The results from this function can be passed to the theme_item_list function
+ * and then themed as a file tree..
+ * 
+ * @param $files_list
+ *   The array as returned by tripal_user_get_files().
+ * 
+ * @return 
+ *   An item list.
+ */
+function tripal_user_get_files_item_list($files_list, &$i = 0) {
+  $items = [];
+  
+  // Add a header row
+  if ($i == 0) {
+    $items[] = [
+      'data' => '<span><b>File</b></span>' .
+      '<span class="file-expires"><b>Expires</b></span>' .
+      '<span class="file-size"><b>Size</b></span>' ,
+    ];
+  }
+  
+  // Iterate through each file and recursively add it to our items array.
+  foreach ($files_list as $filename => $file) {
+    $i++;
+    // If this is a folder then recurse.
+    if (is_array($file)) {
+      $items[] = [
+        'data' => $filename,
+        'children' => tripal_user_get_files_item_list($file, $i),
+        'class' => ['tree-node-folder', 'tree-node-closed', ($i % 2 == 0) ? 'even' : 'odd'],
+      ];
+    }
+    // If this is a file then give details for it.
+    else {
+      $datediff = $file->expiration_date - time();
+      $dayleft = round($datediff / (60 * 60 * 24));
+      if ($dayleft < 0) {
+        $dayleft = 0;
+      }
+      $expiration = $file->expiration_date ? date('Y-m-d', $file->expiration_date) : '';
+      $items[] = [
+        'data' => '<span class="file-details"><span class="file-name">' . $filename . '</span>' . 
+          '<span class="file-expires">' . $dayleft . ' days</span>' . 
+          '<span class="file-size">' . tripal_format_bytes($file->filesize) . '</span></span>' ,
+        'class' => ['tree-node-file', ($i % 2 == 0) ? 'even' : 'odd'],
+        'fid' => $file->fid,
+        'uid' => $file->uid,
+      ];
+    }
+  }
+  
+  return $items;
+}
 
-    $rows[] = array(
-      $entry->fid,
-      l($file->filename,"/user/$uid/files/$file->fid"),
-      $date_uploaded,
-      $expiration,
-      tripal_format_bytes($file->filesize),
-      $actions,
-    );
+/**
+ * Gets the list of collections that have not yet generated files.
+ * 
+ * @param $uid
+ *   The ID of the user.
+ */
+function tripal_user_files_get_pending_collections_table($uid) {
+  
+  $collections = db_select('tripal_collection', 'tc')
+    ->fields('tc', array('collection_id'))
+    ->condition('uid', $uid)
+    ->orderBy('tc.collection_name')
+    ->execute();
+
+  $headers = array('Name', 'Download Formats', 'Actions');
+  $rows = array();
+  
+  while ($collection_id = $collections->fetchField()) {
+    $collection = new TripalEntityCollection();
+    $collection->load($collection_id);
+    
+    $downloads = array();
+    $formatters = $collection->getFormatters();
+    $formatter_labels = [];
+    
+    $status = 'complete';
+    foreach ($formatters as $class_name => $label) {
+      $formatter_labels[] = $label;
+      
+      $outfile = $collection->getOutfilePath($class_name);
+      
+      if (file_exists($outfile)) {
+        continue;
+      }
+      else {
+        $status = 'pending';
+      }
+    }
+    
+    if ($status == 'pending') {    
+      $rows[] = array(
+        'data' => array(
+          $collection->getName(),
+          implode(', ', $formatters),
+          l('Create Files', 'user/' . $uid . '/data-collections/generate/' . $collection_id) . ' | ' .
+          l('View', 'user/' . $uid . '/data-collections/' . $collection_id . '/view') . ' | ' .
+          l('Delete', 'user/' . $uid . '/data-collections/' . $collection_id . '/delete'),
+        ),
+      );
+    }
   }
-  $header = array('ID', 'File Name', 'Upload Date', 'Expiration', 'Size', 'Actions');
+  return theme_table(array(
+      'header' => $headers,
+      'rows' => $rows,
+      'attributes' => array(),
+      'caption' => '',
+      'colgroups' => array(),
+      'sticky' => TRUE,
+      'empty' => t('You currently have no pending data collections.')
+    ));
+}
+/**
+ * Provides the page with a list of files uploaded by the user.
+ *
+ * @param $uid
+ *   The user ID.
+ *
+ * @return
+ *   A Drupal render array.
+ */
+ function tripal_user_files_page($uid) {
+
+  drupal_add_css(drupal_get_path('module', 'tripal') . '/theme/css/tripal_user_files.css');
+  drupal_add_js(drupal_get_path('module', 'tripal') . '/theme/js/tripal.user_files.js', 'file');
+   
+  $user_files = tripal_user_get_files($uid);
+  $items = tripal_user_get_files_item_list($user_files);  
+  $theme_files = theme_item_list([
+    'items' => $items,
+    'title' => '',
+    'type' => 'ul',
+    'attributes' => [
+      'id' => 'tripal-user-file-tree'
+    ],
+  ]);
+   
+  $data_collections = tripal_user_files_get_pending_collections_table($uid);
 
   // Get the user quota settings.
   $quota = tripal_get_user_quota($uid);
@@ -56,11 +214,11 @@
   $content = array(
     'page_title' => array(
       '#type' => 'markup',
-      '#markup' => '<h2>Your Uploaded Files</h2>',
+      '#markup' => '<h2>Your Files</h2>',
     ),
     'page_description' => array(
       '#type' => 'markup',
-      '#markup' => '<p>' . t('Each user is allowed to consume a limited amount of space with uploaded files. This page provides details about your current usage, your limits and files you\'ve uploaded.') . '</p>',
+      '#markup' => '<p>' . t('Each user is allowed to consume a limited amount of space for files. This page provides details about your current usage, your limits and files in your account.') . '</p>',
     ),
     'usage' => array(
       '#type' => 'item',
@@ -78,20 +236,26 @@
       '#type' => 'item',
       '#title' => 'Current Days to Expire',
       '#markup' => $quota->custom_expiration,
-      '#description' => t('The number of days a file will remain on the server before deletion. The expiration of date of a file can be renewed using the "Renew" link in the table below.')
+      '#description' => t('The number of days a file will remain on the server before deletion. The expiration of date of a file can be renewed by selecting the file name and then selecting the "Renew" link in the file details table.')
+    ),
+    'data_collections' => array(
+      '#type' => 'item',
+      '#title' => 'Pending Data Collections',
+      '#markup' => $data_collections,
+      '#description' =>  t('Data collections allow you to store custom sets of data
+       for use on this site.  Typically data collections are created using search 
+       tools.  The above data collections are waiting to be generated. You must
+       generate the files before you can use them.'),
     ),
     'file_list' => array(
       '#type' => 'item',
-      '#title' => 'Uploaded Files',
-      '#markup' => theme_table(array(
-        'header' => $header,
-        'rows' => $rows,
-        'attributes' => array(),
-        'caption' => t('Click a file name for more details.'),
-        'colgroups' => array(),
-        'sticky' => TRUE,
-        'empty' => 'You currently have no uploaded files.',
-      )),
+      '#title' => 'File Browser',
+      '#markup' => $theme_files,
+    ),
+    'file_details' => array(
+      '#type' => 'item',
+      '#title' => 'File Details',
+      '#markup' => '<div id="tripal-user-file-details">Click a file above for details.</div>',
     )
   );
 
@@ -211,12 +375,59 @@ function tripal_delete_file_form_submit($form, &$form_state) {
  */
 function tripal_view_file($uid, $fid) {
   $file = file_load($fid);
+  $usage = file_usage_list($file);
+  
+  // Check to see if this is a data collection.  
+  $collection = NULL;
+  $collection_ctypes = [];
+  $collection_field_list = [];
+  $collection_formatters = [];
+  $collection_entities = 0;
+  if (array_key_exists('tripal', $usage)) {
+    if (array_key_exists('data_collection', $usage['tripal'])) {
+      $collection_id = array_keys($usage['tripal']['data_collection'])[0];
+      $collection = new TripalEntityCollection();
+      $collection->load($collection_id);
+      
+      // Get the content types for this data collection.
+      $cbundles = $collection->getBundles();     
+      foreach ($cbundles as $cbundle) {
+        $eids = $collection->getEntityIDs($cbundle->bundle_name);
+        $fields = $collection->getFieldIDs($cbundle->bundle_name);
+        
+        // Convert local field IDs to their names.
+        if (!$cbundle->site_id) {
+          $bundle = tripal_load_bundle_entity(array('name' => $cbundle->bundle_name));
+          $collection_ctypes[] = $bundle->label;
+          
+          foreach ($fields as $field_id) {
+            $field = field_info_field_by_id($field_id);
+            $instance = field_info_instance('TripalEntity', $field['field_name'], $bundle->name);
+            $collection_field_list[] = $instance['label'];
+            
+            $field_formatters = tripal_get_field_field_formatters($field, $instance);
+            foreach ($field_formatters as $class_name => $label) {
+              tripal_load_include_downloader_class($class_name);
+              $collection_formatters[] = $class_name::$label . ' (' . $class_name::$full_label . ')';
+            }
+          }
+        }
+        // Convert remote field IDs to their names.
+        // TODO: add in retrieval of remote details.
+        
+        
+        $collection_entities += count($eids);
+      }
+      
+    }
+  }
 
   $headers = array();
   $rows = array();
 
   $actions = l('Delete', "user/$uid/files/$file->fid/delete") . '<br>' .
-             l('Download', "user/$uid/files/$file->fid/download");
+             l('Download', "user/$uid/files/$file->fid/download") . '<br>' .
+             l('Renew', "user/$uid/files/$file->fid/renew");
 
   // Name row
   $rows[] = array(
@@ -231,7 +442,7 @@ function tripal_view_file($uid, $fid) {
   $date_uploaded = date('Y-m-d H:i:s', $file->timestamp);
   $rows[] = array(
     array(
-      'data' => 'Upload Date',
+      'data' => 'Create Date',
       'header' => TRUE,
       'width' => '20%',
     ),
@@ -252,7 +463,18 @@ function tripal_view_file($uid, $fid) {
     ),
     $expiration
   );
-
+  
+  $md5_file = $file->uri . '.md5';
+  if (file_exists($md5_file)) {
+    $rows[] = array(
+      array(
+        'data' => 'MD5',
+        'header' => TRUE,
+        'width' => '20%',
+      ),
+      file_get_contents($md5_file),
+    );
+  } 
   $rows[] = array(
     array(
       'data' => 'Actions',
@@ -262,29 +484,82 @@ function tripal_view_file($uid, $fid) {
     $actions
   );
 
-  $content = array(
-    'description' => array(
-      '#type' => 'markup',
-      '#markup' => '<p>' . t('The following file has been uploaded.') . '</p>',
-    ),
-    'return' => array(
-      '#type' => 'markup',
-      '#markup' => '<p>' . l('View all Uploaded Files', "user/$uid/files") . '</p>',
-    ),
-    'file_details' => array(
-      '#type' => 'markup',
-      '#markup' => theme_table(array(
-        'header' => $headers,
-        'rows' => $rows,
-        'attributes' => array(),
-        'sticky' => FALSE,
-        'caption' => '',
-        'colgroups' => array(),
-        'empty' => '',
-      )),
-    ),
-  );
-  return $content;
+  $file_content = theme_table([
+    'header' => $headers,
+    'rows' => $rows,
+    'attributes' => [],
+    'sticky' => FALSE,
+    'caption' => '',
+    'colgroups' => [],
+    'empty' => '',
+  ]);
+  
+  
+  $collection_content = '';
+  if ($collection) {
+    $rows = [];
+    $rows[] = array(
+      array(
+        'data' => 'Description',
+        'header' => TRUE,
+        'width' => '20%',
+      ),
+      $collection->getDescription(),
+    );
+    $rows[] = array(
+      array(
+        'data' => 'Content types',
+        'header' => TRUE,
+        'width' => '20%',
+      ),
+      join(', ', $collection_ctypes),
+    );
+    
+    $rows[] = array(
+      array(
+        'data' => 'Fields',
+        'header' => TRUE,
+        'width' => '20%',
+      ),
+      join(', ', array_unique($collection_field_list)),
+    );
+    $rows[] = array(
+      array(
+        'data' => 'Supported File Types',
+        'header' => TRUE,
+        'width' => '20%',
+      ),
+      join(', ', array_unique($collection_formatters)),
+    );
+    $rows[] = array(
+      array(
+        'data' => 'Records',
+        'header' => TRUE,
+        'width' => '20%',
+      ),
+      number_format($collection_entities),
+    );
+    $rows[] = array(
+      array(
+        'data' => 'Actions',
+        'header' => TRUE,
+        'width' => '20%',
+      ),
+      l('Delete', 'user/' . $uid . '/data-collections/' . $collection->getCollectionID() . '/delete')
+    );
+    
+    $collection_content = '<b>Collection Details</b>' . theme_table([
+      'header' => $headers,
+      'rows' => $rows,
+      'attributes' => [],
+      'sticky' => FALSE,
+      'caption' => '',
+      'colgroups' => [],
+      'empty' => '',
+    ]);
+  }
+  
+  drupal_json_output($file_content . $collection_content);
 }
 
 

+ 56 - 0
tripal/theme/css/tripal_user_files.css

@@ -0,0 +1,56 @@
+#tripal-user-file-tree {
+  margin: 0;
+  padding: 10px;
+  height: 250px;
+  overflow: auto;
+  border: 1px solid #AAAAAA;
+}
+#tripal-user-file-tree li.tree-node-folder {
+  background: url(../images/directory.png) no-repeat;
+  background-size: 22px 22px;
+  cursor: pointer;
+}
+#tripal-user-file-tree li.tree-node-file {
+  background: url(../images/txt.png) no-repeat;
+  background-size: 22px 22px;
+  cursor: pointer;
+}
+#tripal-user-file-tree li.odd {
+  
+}
+#tripal-user-file-tree li.even {
+  background-color: #EEEEEE;
+}
+#tripal-user-file-tree ul {
+  margin: 0;
+  padding: 0;
+}
+#tripal-user-file-tree li {
+	list-style-type: none;
+  margin: 0px;
+  padding: 0px 0px 0px 30px;
+  line-height: 22px;
+}
+#tripal-user-file-tree .file-name {
+  color: #369;	
+}
+#tripal-user-file-tree .file-size {
+  float: right; 
+  padding-left: 10px;
+  font-size: 0.8em; 
+  width: 60px;
+  text-align: right;
+}
+#tripal-user-file-tree .file-expires {
+  float: right; 
+  padding-left: 10px;
+  font-size: 0.8em; 
+  width: 60px;
+  text-align: right;
+}
+#tripal-user-file-tree a {
+  border-bottom-width: 1px;
+  border-bottom-style: dotted;
+}
+#tripal-user-file-details {
+}

BIN
tripal/theme/images/directory.png


BIN
tripal/theme/images/txt.png


+ 77 - 0
tripal/theme/js/tripal.user_files.js

@@ -0,0 +1,77 @@
+(function ($) {
+  
+  Drupal.behaviors.TripalUserFiles = {
+    attach: function (context, settings) {
+
+      // The Drupal theme_items_list duplicates the classes of the li on
+      // the ul of nexted children.  This screws up our collapse/open so
+      // we'll remove it.
+      $('#tripal-user-file-tree ul').removeAttr('class');
+      
+      // Set default actions for closed and open folders.
+      $('.tree-node-closed').children().hide();
+      $('.tree-node-closed').click(function(event) {
+        expandNode($(this));
+      });
+      $('.tree-node-open').click(function(event) {
+        collapseNode($(this));
+      });
+      
+      // Keep clicks on the files from propagating up to folders and
+      // causing collapse.
+      $('.tree-node-file').click(function(event) {
+      	event.stopPropagation();
+      	
+      	// Reset the colors for all of the elements.
+        $('li.even').css("background-color", "#EEEEEE");
+        $('li.odd').css("background-color", "#FFFFFF");
+        
+        // Get the file details.
+      	showFileDetails($(this));
+      	
+      	// Higlight the selected file.
+      	$(this).css("background-color", "#FFAAAA");
+      });
+    }
+  }
+
+  /**
+   * Prints the details of the selected file from the tree.
+   */
+  function showFileDetails(item) {  
+	var fid = item.attr('fid');
+	var uid = item.attr('uid');
+    $.ajax({
+      url : baseurl + '/user/' + uid + '/files/' + fid,
+      success: function(data) {
+        $('#tripal-user-file-details').html(data);
+      }
+    });  	
+  }
+  
+  /**
+   * Collapses a node in the CV tree browser and removes its children.
+   */
+  function collapseNode(item) {
+    item.removeClass('tree-node-open');
+    item.addClass('tree-node-closed');
+    item.children().hide()
+    item.unbind('click');
+    item.click(function(event){
+      expandNode($(this));
+    })
+  }
+  
+  /**
+   * Expands a node in the CV tree browser and loads it's children via AJAX.
+   */
+  function expandNode(item){
+    item.removeClass('tree-node-closed');
+    item.addClass('tree-node-open');
+    item.children().show()
+    item.unbind('click');
+    item.click(function(event){
+      collapseNode($(this));
+    }) 
+  }
+})(jQuery);

+ 8 - 18
tripal/tripal.module

@@ -358,17 +358,6 @@ function tripal_menu() {
   /**
    * Data Collections
    */
-  $items['user/%/data-collections'] = array (
-    'title' => 'Data Collections',
-    'description' => 'Your list of saved data collections',
-    'page callback' => 'tripal_user_collections_page',
-    'access callback' => 'tripal_accesss_user_collections',
-    'access arguments' => array(1),
-    'type' => MENU_LOCAL_TASK,
-    'file' => 'includes/tripal.collections.inc',
-    'file path' => drupal_get_path('module', 'tripal'),
-  );
-
   $items['user/%/data-collections/%/delete'] = array (
     'title' => 'Delete a Collections',
     'description' => 'Deletes a data collection.',
@@ -511,11 +500,11 @@ function tripal_menu() {
   
   // User view quota (Tab)
   $items['user/%/files'] = [
-    'title' => 'Uploads',
+    'title' => 'Files',
     'description' => 'Monitors what files that have been uploaded by user through the tripal module',
     'page callback' => 'tripal_user_files_page',
     'page arguments' => [1],
-    'access callback' => 'tripal_access_user_uploads',
+    'access callback' => 'tripal_access_user_files',
     'access arguments' => ['view', 1],
     'type' => MENU_LOCAL_TASK,
     'file' => 'includes/tripal.user.inc',
@@ -528,19 +517,20 @@ function tripal_menu() {
     'description' => "View details about the file",
     'page callback' => 'tripal_view_file',
     'page arguments' => [1, 3],
-    'access callback' => 'tripal_access_user_uploads',
+    'access callback' => 'tripal_access_user_files',
     'access arguments' => ['renew', 1, 3],
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal.user.inc',
     'file path' => drupal_get_path('module', 'tripal'),
   ];
+  
   // User file renew.
   $items['user/%/files/%/renew'] = [
     'title' => 'Renew File',
     'description' => "Renew a user's file",
     'page callback' => 'tripal_renew_file',
     'page arguments' => [3],
-    'access callback' => 'tripal_access_user_uploads',
+    'access callback' => 'tripal_access_user_files',
     'access arguments' => ['renew', 1, 3],
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal.user.inc',
@@ -553,7 +543,7 @@ function tripal_menu() {
     'page callback' => 'tripal_download_file',
     'page arguments' => [3],
     'access arguments' => ['download', 1, 3],
-    'access callback' => 'tripal_access_user_uploads',
+    'access callback' => 'tripal_access_user_files',
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal.user.inc',
     'file path' => drupal_get_path('module', 'tripal'),
@@ -564,7 +554,7 @@ function tripal_menu() {
     'description' => "Delete a user's file based on either user action or expired file",
     'page callback' => 'drupal_get_form',
     'page arguments' => ['tripal_delete_file_form', 1, 3],
-    'access callback' => 'tripal_access_user_uploads',
+    'access callback' => 'tripal_access_user_files',
     'access arguments' => ['delete', 1, 3],
     'type' => MENU_CALLBACK,
     'file' => 'includes/tripal.user.inc',
@@ -584,7 +574,7 @@ function tripal_menu() {
  * @param $fid
  *   The file ID.
  */
-function tripal_access_user_uploads($op, $uid, $fid = NULL) {
+function tripal_access_user_files($op, $uid, $fid = NULL) {
   global $user;
   
   // The site admin can do anything.

+ 1 - 1
tripal/tripal_views_query.inc

@@ -277,7 +277,7 @@ class tripal_views_query extends views_plugin_query {
           // really should create a new tripal_views_plugin_pager class
           // and call the corresponding function here, but due to time
           // constraints this is the shortcut.
-          $total_items = $cquery->execute();
+          $total_items = $this->cquery->execute();
           $this->pager->total_items = $total_items;
           if (!empty($this->pager->options['offset'])) {
             $this->pager->total_items -= $this->pager->options['offset'];

+ 6 - 2
tripal/views_handlers/tripal_views_handler_area_collections.inc

@@ -16,6 +16,10 @@ class tripal_views_handler_area_collections extends views_handler_area_result {
     if (!$collections_enabled) {
       return '';
     }
+    
+    if (user_is_anonymous()) {
+      return '';
+    }
 
     // We need a specific form to work with Tripal content types and the tripal_views_query plugin.
     if ($this->query->plugin_name == 'tripal_views_query') {
@@ -458,11 +462,11 @@ function tripal_views_handler_area_collections_search_api_form_submit($form, $fo
   }
 
   // Finally, tell the user we're done and link them to the collection.
-  drupal_set_message(t("Collection '%name' created with %num_recs record(s). Check the !view to generate file links.",
+  drupal_set_message(t("Collection '%name' created with %num_recs record(s). Files for this collection have not yet been created. To create files for download or use with other tools, check the !view.",
     array(
       '%name' => $collection_details['collection_name'],
       '%num_recs' => count($results),
-      '!view' => l('data collections page', 'user/' . $user->uid . '/data-collections'),
+      '!view' => l('files page', 'user/' . $user->uid . '/files'),
     ))
   );
 }

+ 7 - 0
tripal_chado/api/tripal_chado.mviews.api.inc

@@ -355,6 +355,13 @@ function chado_delete_mview($mview_id) {
       $sql = "DROP TABLE {" . $mview->mv_table . "}";
       $success = chado_query($sql);
       if ($success) {
+        //unset the variable
+        global $databases;
+        $default_db = $databases['default']['default']['database'];
+        $chado_schema = chado_get_schema_name('chado');
+        if (isset($GLOBALS["chado_tables"][$default_db][$chado_schema][$mview->mv_table])){
+          unset($GLOBALS["chado_tables"][$default_db][$chado_schema][$mview->mv_table]);
+        }
         drupal_set_message(t("Materialized view, %name, deleted.", array('%name' => $mview->name)));
         return TRUE;
       }

+ 47 - 0
tripal_chado/api/tripal_chado.semweb.api.inc

@@ -215,6 +215,53 @@ function chado_get_semweb_term($chado_table, $chado_column, $options = array())
   }
 }
 
+/**
+ * Retrieves the terms that maps to the given Chado table.
+ *
+ * @param $chado_table
+ *   The name of the Chado table.
+ * @param $options
+ *   An associative array of one or more of the following keys:
+ *     -return_object:  Set to TRUE to return the cvterm object rather than
+ *      the string version of the term.
+ *
+ * @return
+ *   An array of terms with the table column name as the key and the term 
+ *   details as the avlue. If the 'return_object' options is provided then 
+ *   a cvterm object is used as the value. A NULL value is used if no term is 
+ *   mapped to a column.
+ *
+ *  @ingroup tripal_chado_semweb_api
+ */
+function chado_get_semweb_terms($chado_table, $options = array()) {
+  
+  $terms = [];
+  
+  $schema = chado_get_schema($chado_table);
+  foreach ($schema['fields'] as $chado_column => $details) {
+    $terms[$chado_column] = NULL;
+    
+    $cvterm_id = db_select('chado_semweb', 'CS')
+      ->fields('CS', array('cvterm_id'))
+      ->condition('chado_column', $chado_column)
+      ->condition('chado_table', $chado_table)
+      ->execute()
+      ->fetchField();
+    
+    if ($cvterm_id) {
+      $cvterm = chado_generate_var('cvterm', array('cvterm_id' => $cvterm_id));
+      if (array_key_exists('return_object', $options)) {
+        $terms[$chado_column] = $cvterm;
+      }
+      else {
+        $terms[$chado_column] = chado_format_semweb_term($cvterm);
+      }
+    }
+    
+  }
+  return $terms;
+}
+
 /**
  * Formats a controlled vocabulary term from Chado for use with Tripal.
  *

+ 1 - 1
tripal_chado/includes/TripalFields/local__contact/local__contact_widget.inc

@@ -106,7 +106,7 @@ class local__contact_widget extends ChadoFieldWidget {
     }
     // If no name is provided then we want to set the field for deletion.
     else {
-      $form_state['values'][$field_name]['und'][$delta][$linker_field] = '';
+      $form_state['values'][$field_name]['und'][$delta][$linker_field] = '__NULL__';
     }
   }
 }

+ 5 - 1
tripal_chado/includes/TripalFields/obi__organism/obi__organism.inc

@@ -69,7 +69,7 @@ class obi__organism extends ChadoField {
 
       // Get the field values.
       $organism_id = $values[$linker_field];
-      if (!$organism_id or $organism_id == 0) {
+      if ((!$organism_id or $organism_id == 0) and !$field_table == 'biomaterial') {
         $errors[$field_name]['und'][0][] = array(
           'message' =>  t("Please specify an organism."),
           'error' => 'obi__organism_id'
@@ -119,6 +119,10 @@ class obi__organism extends ChadoField {
       else {
         $organism = $record->organism_id;
       }
+      
+      if (!$organism) {
+        return;
+      }
       $string = $settings['field_display_string'];
       $label = chado_replace_tokens($string, $organism);
       $entity->{$field_name}['und'][0]['value'] = array(

+ 1 - 1
tripal_chado/includes/TripalFields/obi__organism/obi__organism_formatter.inc

@@ -12,7 +12,7 @@ class obi__organism_formatter extends ChadoFieldFormatter {
    * @see TripalFieldFormatter::view()
    */
   public function view(&$element, $entity_type, $entity, $langcode, $items, $display) {
-    if (count($items) > 0) {
+    if ($items[0]['value']) {
       $content = $items[0]['value']['rdfs:label'];
       if (array_key_exists('entity', $items[0]['value'])) {
         list($entity_type, $entity_id) = explode(':', $items[0]['value']['entity']);

+ 8 - 1
tripal_chado/includes/TripalFields/obi__organism/obi__organism_widget.inc

@@ -70,6 +70,13 @@ class obi__organism_widget extends ChadoFieldWidget {
 
     // Make sure the value is set to the organism_id
     $organism_id = $form_state['values'][$field_name]['und'][0][$linker_field];
-    $form_state['values'][$field_name]['und'][0]['value'] = $organism_id;
+    if ($organism_id > 0) {
+      $form_state['values'][$field_name]['und'][0]['value'] = $organism_id;
+      $form_state['values'][$field_name]['und'][$delta][$linker_field] = $organism_id;
+    }
+    else {
+      $form_state['values'][$field_name]['und'][0]['value'] = '';
+      $form_state['values'][$field_name]['und'][$delta][$linker_field] = '__NULL__';
+    }
   }
 }

+ 1 - 1
tripal_chado/includes/TripalFields/sep__protocol/sep__protocol_widget.inc

@@ -89,7 +89,7 @@ class sep__protocol_widget extends ChadoFieldWidget {
       $form_state['values'][$field_name]['und'][0]['value'] = $protocol_id;
     }
     else {
-      $form_state['values'][$field_name]['und'][0]['value'] = '__NULL__';
+      $form_state['values'][$field_name]['und'][0]['value'] = '';
       $form_state['values'][$field_name]['und'][0][$linker_field] = '__NULL__';
     }
   }

+ 179 - 150
tripal_chado/includes/tripal_chado.fields.inc

@@ -712,22 +712,10 @@ function tripal_chado_bundle_fields_info_linker(&$info, $details, $entity_type,
   // PROPERTIES
   $prop_table = $table_name . 'prop';
   if (chado_table_exists($prop_table)) {
-    // Get the list of existing property types for this table.
-    $sql = 'SELECT DISTINCT type_id FROM {' . $prop_table . '}';
-    $props = chado_query($sql);
-    while ($prop = $props->fetchObject()) {
-      $term = chado_generate_var('cvterm', array('cvterm_id' => $prop->type_id));
-
-      // The tripal_analysis_KEGG, tripal_analysis_blast, and
-      // tripal_analysis_interpro modules store results in the analysisprop
-      // table which is probably not the best place, but we don't want to
-      // create a ton of fields for this, so skip them.
-      if ($prop_table == 'analysisprop' and
-          ($term->dbxref_id->db_id->name == 'KEGG_BRITE' or
-           $term->dbxref_id->db_id->name == 'tripal')) {
-        continue;
-      }
-
+    
+    $props = tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column);
+    foreach ($props as $term) {
+          
       $field_name = strtolower(preg_replace('/[^\w]/','_', $term->dbxref_id->db_id->name . '__' . $term->name));
 
       // The field name can only be 32 chars, but if our name is longer we need
@@ -2489,144 +2477,63 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
   // PROPERTIES
   $prop_table = $table_name . 'prop';
   if (chado_table_exists($prop_table)) {
-     $tschema = chado_get_schema($table_name);
-     $schema = chado_get_schema($prop_table);
-     $tpkey = $tschema['primary key'][0];
-     $pkey = $schema['primary key'][0];
-
-     // Property tables can be a bit tricky because not all property types
-     // in the prop table are appropriate for each type of data.  Also som
-     // bundle types are resolved via a property.  So, we have to distinguish
-     // between these two cases.
-     $sql = '';
-     $args = array();
-
-     // First, is this the case where all of the records in the table are
-     // of this type?  If so, then all properties apply
-     if (!$type_column) {
-        $sql = 'SELECT DISTINCT type_id FROM {' . $prop_table . '}';
-        $props = chado_query($sql, $args);
-     }
-     // Second, if this is the case where a content type is uniquely identified
-     // by a type_id value in the base table, then only properties associated
-     // with that type ID should be used.
-     else if ($type_column and !$type_table) {
-      $sql = "
-        SELECT DISTINCT P.type_id
-        FROM {" . $prop_table . "} P
-          INNER JOIN {" . $table_name . "} T on T.$tpkey = P.$tpkey
-        WHERE T.$type_column = :cvterm_id
-      ";
-      $args[':cvterm_id'] = $cvterm_id;
-      $props = chado_query($sql, $args);
-     }
-     // Third, if this is the case where a content type is uniquely identified
-     // via a term/value pair in the prop table.
-     else if ($type_column and $type_table == $prop_table and !empty($type_value)) {
-       $sql = "
-        SELECT DISTINCT P2.type_id
-        FROM {" . $prop_table . "} P1
-          INNER JOIN {" . $table_name . "} T on T.$tpkey = P1.$tpkey
-          INNER JOIN {" . $prop_table . "} P2 on T.$tpkey = P2.$tpkey
-        WHERE P1.$type_column = :cvterm_id AND P1.value = :prop_value AND
-          P2.type_id != P1.type_id
-       ";
-       $args[':cvterm_id'] = $cvterm_id;
-       $args[':prop_value'] = $type_value;
-       $props = chado_query($sql, $args);
-     }
-     // Fourth, if this is the case where the content type is uinquely identifed
-     // via another table (e.g. cvterm linking table) and not this prop table.
-     else if ($type_column and $type_table != $prop_table and empty($type_value)) {
-       $sql = "
-         SELECT DISTINCT P.type_id
-         FROM {" . $prop_table . "} P
-           INNER JOIN {" . $table_name . "} T on T.$tpkey = P.$tpkey
-           INNER JOIN {" . $type_table . "} TT on TT.$tpkey = T.$tpkey
-         WHERE TT.$type_column = :cvterm_id
-       ";
-       $args[':cvterm_id'] = $cvterm_id;
-       $props = chado_query($sql, $args);
-     }
-     else {
-       // Do nothing;
-     }
 
-     if ($props) {
-       while ($prop = $props->fetchObject()) {
-
-         $term = chado_generate_var('cvterm', array('cvterm_id' => $prop->type_id));
-         $term = chado_expand_var($term, 'field', 'cvterm.definition');
-
-         // Skip the Publiation Type property for pubs as this is
-         // already handled by the pub.type_id field.
-         if ($term->name == 'Publication Type' and $term->cv_id->name == 'tripal_pub') {
-           continue;
-         }
-
-         // The tripal_analysis_KEGG, tripal_analysis_blast, and
-         // tripal_analysis_interpro modules store results in the analysisprop
-         // table which is probably not the best place, but we don't want to
-         // create a ton of fields for this.
-         if ($prop_table == 'analysisprop' and
-             ($term->dbxref_id->db_id->name == 'KEGG_BRITE' or
-              $term->dbxref_id->db_id->name == 'tripal')) {
-           continue;
-         }
-
-         $field_name = strtolower(preg_replace('/[^\w]/','_', $term->dbxref_id->db_id->name . '__' . $term->name));
-         // The field name can only be 32 chars, but if our name is longer we need
-         // to add some random chars to ensure we don't have naming conflicts
-         // with other terms (e.g. mitochondrial_genetic_code and
-         // mitochondrial_genetic_code_name)
-         if (strlen($field_name) >= 32) {
-           $field_name = substr($field_name, 0, 20) . '_' . $term->cvterm_id;
-         }
-         $info[$field_name] = array(
-           'field_name' => $field_name,
-           'entity_type' => $entity_type,
-           'bundle' => $bundle->name,
-           'label' => ucwords(preg_replace('/_/', ' ', $term->name)),
-           'description' => $term->definition,
-           'required' => FALSE,
+    $props = tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column);
+    foreach ($props as $term) {     
+
+       $field_name = strtolower(preg_replace('/[^\w]/','_', $term->dbxref_id->db_id->name . '__' . $term->name));
+       
+       // The field name can only be 32 chars, but if our name is longer we need
+       // to add some random chars to ensure we don't have naming conflicts
+       // with other terms (e.g. mitochondrial_genetic_code and
+       // mitochondrial_genetic_code_name)
+       if (strlen($field_name) >= 32) {
+         $field_name = substr($field_name, 0, 20) . '_' . $term->cvterm_id;
+       }
+       $info[$field_name] = array(
+         'field_name' => $field_name,
+         'entity_type' => $entity_type,
+         'bundle' => $bundle->name,
+         'label' => ucwords(preg_replace('/_/', ' ', $term->name)),
+         'description' => $term->definition,
+         'required' => FALSE,
+         'settings' => array(
+           'auto_attach' => TRUE,
+           'term_vocabulary' => $term->dbxref_id->db_id->name,
+           'term_accession' => $term->dbxref_id->accession,
+           'term_name' => $term->name,
+           'base_table' => $table_name,
+           'chado_table' => $prop_table,
+           'chado_column' => $pkey,
+         ),
+         'widget' => array(
+           'type' => 'chado_linker__prop_widget',
            'settings' => array(
-             'auto_attach' => TRUE,
-             'term_vocabulary' => $term->dbxref_id->db_id->name,
-             'term_accession' => $term->dbxref_id->accession,
-             'term_name' => $term->name,
-             'base_table' => $table_name,
-             'chado_table' => $prop_table,
-             'chado_column' => $pkey,
-           ),
-           'widget' => array(
-             'type' => 'chado_linker__prop_widget',
-             'settings' => array(
-               'display_label' => 1,
-             ),
+             'display_label' => 1,
            ),
-           'display' => array(
-             'default' => array(
-               'label' => 'hidden',
-               'type' => 'chado_linker__prop_formatter',
-               'settings' => array(),
-             ),
+         ),
+         'display' => array(
+           'default' => array(
+             'label' => 'hidden',
+             'type' => 'chado_linker__prop_formatter',
+             'settings' => array(),
            ),
-         );
-
-         // Make some customizations to some fields
-         if ($term->name == 'Citation' and $term->cv_id->name == 'tripal_pub') {
-           $info[$field_name]['required'] = TRUE;
-           $info[$field_name]['description'] = t('All publications must have a unique citation.
-            Please enter the full citation for this publication.  For PubMed style citations list
-            the last name of the author followed by initials. Each author should be separated by a comma. Next comes
-            the title, followed by the series title (e.g. journal name), publication date (4 digit year, 3 character Month, day), volume, issue and page numbers. You may also use HTML to provide a link in the citation.
-            Below is an example: <pre>Medeiros PM, Ladio AH, Santos AM, Albuquerque UP. <a href="http://www.ncbi.nlm.nih.gov/pubmed/23462414" target="_blank">Does the selection of medicinal plants by Brazilian local populations
-              suffer taxonomic influence?</a> J Ethnopharmacol. 2013 Apr 19; 146(3):842-52.</pre>');
-           $info[$field_name]['settings']['rows'] = 3;
-         }
-         if ($term->name == 'Abstract' and $term->cv_id->name == 'tripal_pub') {
-           $info[$field_name]['settings']['rows'] = 5;
-         }
+         ),
+       );
+
+       // Make some customizations to some fields
+       if ($term->name == 'Citation' and $term->cv_id->name == 'tripal_pub') {
+         $info[$field_name]['required'] = TRUE;
+         $info[$field_name]['description'] = t('All publications must have a unique citation.
+          Please enter the full citation for this publication.  For PubMed style citations list
+          the last name of the author followed by initials. Each author should be separated by a comma. Next comes
+          the title, followed by the series title (e.g. journal name), publication date (4 digit year, 3 character Month, day), volume, issue and page numbers. You may also use HTML to provide a link in the citation.
+          Below is an example: <pre>Medeiros PM, Ladio AH, Santos AM, Albuquerque UP. <a href="http://www.ncbi.nlm.nih.gov/pubmed/23462414" target="_blank">Does the selection of medicinal plants by Brazilian local populations
+            suffer taxonomic influence?</a> J Ethnopharmacol. 2013 Apr 19; 146(3):842-52.</pre>');
+         $info[$field_name]['settings']['rows'] = 3;
+       }
+       if ($term->name == 'Abstract' and $term->cv_id->name == 'tripal_pub') {
+         $info[$field_name]['settings']['rows'] = 5;
        }
      }
    }
@@ -2828,6 +2735,128 @@ function tripal_chado_bundle_instances_info_linker(&$info, $entity_type, $bundle
 
 }
 
+/**
+ * Used to find all of the properties for a given table.
+ *  
+ * @param $table_name
+ *   The name of the base table.
+ * @param unknown $prop_table
+ *   The name of the property table.
+ * @param $type_table
+ *   The name of the table that contains the type specifier.
+ * @param $type_column
+ *   The name of the column that contains the type specifier.
+ *   
+ * @return
+ *   An array of cvterm objects for the properties to be added as fields.
+ */
+function tripal_chado_bundle_get_properties($table_name, $prop_table, $type_table, $type_column) {
+  
+  $tschema = chado_get_schema($table_name);
+  $schema = chado_get_schema($prop_table);
+  $tpkey = $tschema['primary key'][0];
+  $pkey = $schema['primary key'][0];
+  
+  $props = NULL;
+  
+  // Property tables can be a bit tricky because not all property types
+  // in the prop table are appropriate for each type of data.  Also som
+  // bundle types are resolved via a property.  So, we have to distinguish
+  // between these two cases.
+  $sql = '';
+  $args = array();
+  
+  // First, is this the case where all of the records in the table are
+  // of this type?  If so, then all properties apply
+  if (!$type_column) {
+    $sql = 'SELECT DISTINCT type_id FROM {' . db_escape_table($prop_table) . '}';
+    $props = chado_query($sql, $args);
+  }
+  // Second, if this is the case where a content type is uniquely identified
+  // by a type_id value in the base table, then only properties associated
+  // with that type ID should be used.
+  else if ($type_column and !$type_table) {
+    $sql = "
+        SELECT DISTINCT P.type_id
+        FROM {" . db_escape_table($prop_table) . "} P
+          INNER JOIN {" . db_escape_table($table_name) . "} T on T.$tpkey = P.$tpkey
+        WHERE T.$type_column = :cvterm_id
+      ";
+    $args[':cvterm_id'] = $cvterm_id;
+    $props = chado_query($sql, $args);
+  }
+  // Third, if this is the case where a content type is uniquely identified
+  // via a term/value pair in the prop table.
+  else if ($type_column and $type_table == $prop_table and !empty($type_value)) {
+    $sql = "
+        SELECT DISTINCT P2.type_id
+        FROM {" . db_escape_table($prop_table) . "} P1
+          INNER JOIN {" . db_escape_table($table_name) . "} T on T.$tpkey = P1.$tpkey
+          INNER JOIN {" . db_escape_table($prop_table) . "} P2 on T.$tpkey = P2.$tpkey
+        WHERE P1.$type_column = :cvterm_id AND P1.value = :prop_value AND
+          P2.type_id != P1.type_id
+       ";
+    $args[':cvterm_id'] = $cvterm_id;
+    $args[':prop_value'] = $type_value;
+    $props = chado_query($sql, $args);
+  }
+  // Fourth, if this is the case where the content type is uinquely identifed
+  // via another table (e.g. cvterm linking table) and not this prop table.
+  else if ($type_column and $type_table != $prop_table and empty($type_value)) {
+    $sql = "
+         SELECT DISTINCT P.type_id
+         FROM {" . db_escape_table($prop_table) . "} P
+           INNER JOIN {" . db_escape_table($table_name) . "} T on T.$tpkey = P.$tpkey
+           INNER JOIN {" . db_escape_table($type_table) . "} TT on TT.$tpkey = T.$tpkey
+         WHERE TT.$type_column = :cvterm_id
+       ";
+    $args[':cvterm_id'] = $cvterm_id;
+    $props = chado_query($sql, $args);
+  }
+  
+  // Iterate through all of the properties and do some final checks to see
+  // which ones should be added.
+  $prop_arr = [];
+  while ($prop = $props->fetchObject()) {
+    $term = chado_generate_var('cvterm', array('cvterm_id' => $prop->type_id));
+    $term = chado_expand_var($term, 'field', 'cvterm.definition');
+    
+   
+    // The tripal_analysis_KEGG, tripal_analysis_blast, and
+    // tripal_analysis_interpro modules store results in the analysisprop
+    // table which is probably not the best place, but we don't want to
+    // create a ton of fields for this.
+    if ($prop_table == 'analysisprop' and
+        ($term->dbxref_id->db_id->name == 'KEGG_BRITE' or
+         $term->dbxref_id->db_id->name == 'tripal')) {
+      continue;
+    }
+    
+    // The Tripal publication importer adds properties to publications that
+    // are also represented in the table fields.  We want editing of pub
+    // related fields to always go back to the pub table, so we do not
+    // want prop fields to show up.
+    if ($table_name == 'pub') {
+      $skip_pub_property = FALSE;
+      $pub_terms = chado_get_semweb_terms('pub', ['return_object' => TRUE]);
+      foreach ($pub_terms as $pub_column => $mapped_term) {
+        $term_accession = $term->dbxref_id->db_id->name . ':' . $term->dbxref_id->accession;
+        $mapped_accession = $mapped_term->dbxref_id->db_id->name . ':' . $mapped_term->dbxref_id->accession;
+        if ($term_accession == $mapped_accession) {
+          $skip_pub_property = TRUE;
+        }
+      }
+      if ($skip_pub_property) {
+        continue;
+      }
+    }
+    
+    // Add the term to our list!
+    $prop_arr[] = $term;
+  }
+  return $prop_arr;
+
+}
 /**
  * Implements hook_bundle_create_user_field().
  *

+ 2 - 2
tripal_ws/includes/TripalWebService/TripalContentService_v0_1.inc

@@ -68,7 +68,7 @@ class TripalContentService_v0_1 extends TripalWebService {
       $ctype_lookup = array();
       $found = FALSE;
       while ($bundle = $bundles->fetchObject()) {
-        // Check the label by replacing non alpha-numeric characters with 
+        // Check the label by replacing non alpha-numeric characters with
         // an underscore and is case-insensitive
         $label = preg_replace('/[^\w]/', '_', $bundle->label);
         if (preg_match("/^$label$/i", $ctype)) {
@@ -278,7 +278,7 @@ class TripalContentService_v0_1 extends TripalWebService {
       }
       // Get the information about this field.
       $field = field_info_field($field_name);
-      
+
       // If the field has the $no_data turned on then we should exclude it.
       if (tripal_load_include_field_class($field['type'])) {
         $field_class = $field['type'];

+ 0 - 4
tripal_ws/includes/TripalWebServiceResource.inc

@@ -43,10 +43,6 @@ class TripalWebServiceResource {
 
     // First, add the RDFS and Hydra vocabularies to the context.  All Tripal
     // web services should use these.
-    $vocab = tripal_get_vocabulary_details('rdf');
-
-    $this->addContextItem('rdf', $vocab['sw_url']);
-
     $vocab = tripal_get_vocabulary_details('rdfs');
     $this->addContextItem('rdfs', $vocab['sw_url']);