فهرست منبع

Merge pull request #366 from tripal/7.x-3.x-files_management

7.x 3.x files management
Stephen Ficklin 6 سال پیش
والد
کامیت
7ca7a037c6

+ 64 - 0
tripal/api/tripal.files.api.inc

@@ -125,3 +125,67 @@ function tripal_get_files_stream($module_name = FALSE) {
 
   return $stream;
 }
+
+/**
+ * Formats a size (in bytes) in human readable format.
+ *
+ * Function taken from php.net
+ *
+ * @param $bytes
+ *   The size of the file in bytes
+ * @param $precision
+ *   The number of decimal places to use in the final number if needed
+ *
+ * @return string
+ *   A formatted string indicating the size of the file
+ */
+function tripal_format_bytes($bytes, $precision = 2) {
+  $units = ['B', 'KB', 'MB', 'GB', 'TB'];
+    
+  $bytes = max($bytes, 0);
+  $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+  $pow = min($pow, count($units) - 1);
+    
+  // Uncomment one of the following alternatives
+  $bytes /= pow(1000, $pow);
+  // $bytes /= (1 << (10 * $pow));
+    
+  return round($bytes, $precision) . '' . $units[$pow];
+}
+
+/**
+ * Retrieves the list of files uploaded by a user.
+ * @param $uid
+ *   The ID of the user whose files should be retrieved.
+ * @param $allowed_types
+ *   A list of valid extensions to restrict the files to.
+ * @param $module
+ *   The name of the module that is managing the file.
+ *      
+ * @return
+ *   A list of file objects.
+ */
+function tripal_get_user_uploads($uid, $allowed_types = array(), $module = 'tripal') {
+  $user = user_load($uid);
+  
+  $query = db_select('file_managed', 'FM');
+  $query->fields('FM', array('fid'));
+  $query->distinct();
+  $query->condition('FM.uid', $user->uid);
+  $query->innerJoin('file_usage', 'FU', "FU.fid = FM.fid");
+  $query->condition('FU.module', $module);
+  $query->orderBy('FM.filename');
+  $files = $query->execute();
+  
+  $files_list = [];
+  while ($fid = $files->fetchField()) {
+    $file = file_load($fid);
+    foreach ($allowed_types as $type) {
+      if (preg_match('/\.' . $type . '$/', $file->filename)) {
+        $files_list[$fid] = $file;
+      }
+    }
+  }
+  
+  return $files_list;
+}

+ 3 - 5
tripal/api/tripal.importer.api.inc

@@ -22,10 +22,8 @@
  * This is a Tripal hook that allows the module to set the proper
  * parameters for a file uploaded via the Tripal HTML5 uploader.
  *
- * @param $filename
- *   The name of the file uploaded
- * @param $filepath
- *   The path to the file
+ * @param $file
+ *   The Drupal file object of the newly uploaded file.
  * @param $type
  *   The category or type of file.
  *
@@ -34,7 +32,7 @@
  *
  * @ingroup tripal_importer_api
  */
-function hook_handle_uploaded_file($filename, $filepath, $type) {
+function hook_handle_uploaded_file($file, $type) {
 
 }
 

+ 150 - 0
tripal/api/tripal.quotas.api.inc

@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * Retrieves the user's quote and default days to expire.
+ *
+ * @param $uid
+ *   The User ID.
+ *
+ * @return
+ *   An associative array containing the quota and default days to
+ *   expire.
+ */
+function tripal_get_user_quota($uid) {
+  $quota = db_select('tripal_custom_quota', 'tgcq')->fields('tgcq', [
+    'custom_quota',
+    'custom_expiration'
+  ])
+    ->condition('uid', $uid)
+    ->execute()
+    ->fetchObject();
+  if (! $quota) {
+    $quota = new stdClass();
+    $quota->custom_quota = variable_get('tripal_default_file_quota', pow(20, 6));
+    $quota->custom_expiration = variable_get('tripal_default_file_expiration', '60');
+  }
+  return $quota;
+}
+
+/**
+ * Sets a user's file space quota and default file expiration.
+ *
+ * @param $uid The
+ *          User ID for whom the quota will be set.
+ * @param $quota The
+ *          quota
+ * @param
+ *          $expriation
+ *          
+ * @return The inserted record.
+ */
+function tripal_set_user_quota($uid, $quota, $expriation) {
+  $values = [
+    'uid' => $uid,
+    'custom_quota' => $quota,
+    'custom_expiration' => $expriation
+  ];
+  return db_insert('tripal_custom_quota')->fields($values)->execute();
+}
+
+/**
+ * Removes a user's file space and default file expiration.
+ *
+ * @param $uid The
+ *          User ID for whom the quota will be removed.
+ *          
+ * @return
+ */
+function tripal_remove_user_quota($uid) {
+  db_delete('tripal_custom_quota')->condition('uid', $uid)->execute();
+}
+
+/**
+ * Retrieves the current size of all files uploaded by the user.
+ *
+ * @param $uid The
+ *          User ID.
+ *          
+ * @return The total number of bytes currently used.
+ */
+function tripal_get_user_usage($uid) {
+  // Get the user's current file usage
+  $sql = "
+    SELECT DISTINCT FU.fid
+    FROM {file_usage} FU
+      INNER JOIN {file_managed} FM ON FM.fid = FU.fid and FU.module = 'tripal'
+    WHERE FM.uid = :uid
+  ";
+  $fids = db_query($sql, [
+    ':uid' => $uid
+  ]);
+  $total_size = 0;
+  while ($fid = $fids->fetchObject()) {
+    $sql = "SELECT filesize FROM {file_managed} WHERE fid = :fid";
+    $total_size += db_query($sql, [
+      ':fid' => $fid->fid
+    ])->fetchObject()->filesize;
+  }
+  return $total_size;
+}
+
+/**
+ * Checks if a file needs to be expired.
+ */
+function tripal_expire_files(TripalJob $job = NULL) {
+  $results = db_select('tripal_expiration_files', 'tgfe')
+    ->fields('tgfe')
+    ->execute();
+  while ($result = $results->fetchObject()) {
+    if (time() > $result->expiration_date) {
+      
+      $file = file_load($result->fid);
+      if ($file) {
+        if ($job) {
+          $job->logMessage('File "' . $file->filename . '" has expired. Removing...');
+        }
+        // First remove the file from the file system.
+        file_delete($file, TRUE);
+        
+        // Remove the file from our file expiration table.
+        $query = db_delete('tripal_expiration_files');
+        $query->condition('fid', $result->fid);
+        $query->execute();
+      }
+    }
+  }
+}
+
+/**
+ * Resets the expiration data of a file managed by Tripal.
+ * 
+ * @param $fid
+ *   The file ID of the file to reset.
+ *   
+ * @return
+ *   TRUE on success, FALSE on failure.
+ */
+function tripal_reset_file_expiration($fid) {
+  
+  $file = file_load($fid);
+  try {
+    $quota = tripal_get_user_quota($file->uid);
+    $custom_expiration = $quota->custom_expiration;
+    $expiration_date = time() + $custom_expiration * 24 * 60 * 60;
+    
+    db_delete('tripal_expiration_files')
+       ->condition('fid', $fid)
+       ->execute();
+    db_insert('tripal_expiration_files')
+      ->fields([
+        'fid' => $file->fid,
+        'expiration_date' => $expiration_date,
+      ])
+      ->execute();  
+  }
+  catch (Exception $e) {
+    tripal_report_error('trp_quota', TRIPAL_ERROR, $e->getMessage());
+    return FALSE;
+  }
+  return TRUE;
+}

+ 568 - 0
tripal/includes/tripal.admin_files.inc

@@ -0,0 +1,568 @@
+<?php
+
+/**
+ * Form for adding a file quota for a user.
+ **/
+function tripal_admin_add_custom_form($form, &$form_state) {
+
+  $username = '';
+  $default_quota = variable_get('tripal_default_file_quota', pow(20,6));
+  $default_expiration = variable_get('tripal_default_file_expiration', '60');
+
+  if (array_key_exists('values', $form_state)) {
+    $username = $form_state['values']['username'];
+    $default_quota = $form_state['values']['default_quota'];
+    $default_expiration = $form_state['values']['default_expiration_date'];
+  }
+
+
+  // Textfield (ajax call based off of existing users) for users on the site
+  $form['username'] = array (
+    '#type' => 'textfield',
+    '#title' => 'User',
+    '#autocomplete_path' => 'admin/tripal/files/quota/user/autocomplete',
+    '#default_value' => $username,
+  );
+
+  // Custom quota textfield (prepopulated with defualt value)
+  $form['quota'] = array (
+    '#type' => 'textfield',
+    '#title' => 'Custom User Quota',
+    '#description' => 'Set the number of megabytes that a user can consume. The number must be followed by the suffix "MB" (megabytes) or "GB" (gigabytes) with no space between the number and the suffix (e.g.: 200MB).',
+    '#default_value' => tripal_format_bytes($default_quota),
+  );
+
+  // Custom exp date textfield (prepopulated with defualt value)
+  $form['expiration'] = array (
+    '#type' => 'textfield',
+    '#title' => 'Days to Expire',
+    '#description' => 'The number of days that a user uploaded file can remain on the server before it is automatically removed.',
+    '#default_value' => $default_expiration,
+  );
+
+  // Submit button
+  $form['button'] = array (
+    '#type' => 'submit',
+    '#value' => t('Submit'),
+  );
+  $form['cancel'] = array(
+    '#type' => 'markup',
+    '#markup' => l('Cancel', 'admin/tripal/files/quota'),
+  );
+
+  return $form;
+
+}
+
+/**
+ * Validates the tripal_admin_add_custom_form form.
+ **/
+function tripal_admin_add_custom_form_validate($form, &$form_state) {
+
+  $username = $form_state['values']['username'];
+  $quota = $form_state['values']['quota'];
+  $expiration = $form_state['values']['expiration'];
+
+  // Make sure the username is a valid user.
+  $sql = "SELECT uid FROM {users} WHERE name = :name";
+  $uid = db_query($sql,array(':name' => $username))->fetchField();
+  if (!$uid) {
+    form_set_error('username', 'Cannot find this username');
+  }
+
+  // Does a quota already exist for this user? If so, then don't add it again
+  $check = db_select('tripal_custom_quota', 'tgcq')
+    ->fields('tgcq', array('uid'))
+    ->condition('uid', $uid)
+    ->execute()
+    ->fetchField();
+  if ($check) {
+    form_set_error('username', 'The user "' . $username . '" already has a custom quota set.');
+  }
+
+  // Validate the quota string.
+  if (!preg_match("/^\d+(MB|GB|TB)$/", $quota)) {
+    form_set_error('quota', t('Please provide a quota size in the format indicated.'));
+  }
+
+  // Validate the expiration time.
+  if (!preg_match("/^\d+$/", $expiration)) {
+    form_set_error('expiration', t('Please providate a positive non-decimal numeric value for the days to expire'));
+  }
+}
+
+
+/**
+ * Submiter for the tripal_admin_add_custom_form form.
+ **/
+function tripal_admin_add_custom_form_submit($form, &$form_state) {
+
+  $username = $form_state['values']['username'];
+  $quota = $form_state['values']['quota'];
+  $expiration = $form_state['values']['expiration'];
+
+  // if the 2nd element of the qutoa string occupied by a valid suffix we need to check to see
+  // what we have to multiply the value by (1024 for GB 1024^2 for TB because
+  // we assume that the initial number is already in MB)
+  $matches = array();
+  $multiplier = 'MB';
+  $size = $quota;
+  if (preg_match("/^(\d+)(MB|GB|TB)$/", $quota, $matches)) {
+    $multiplier = $matches[2];
+    $size = $matches[1];
+  }
+
+  switch ($multiplier) {
+    case 'GB':
+      $size = (int) $quota * pow(10,9);
+      break;
+    case 'TB':
+      $size = (int) $quota * pow(10,12);
+      break;
+    default:
+      $size = (int) $quota * pow(10,6);
+      break;
+  }
+
+  // Get the UID of the given user.
+  $sql = "SELECT uid FROM {users} WHERE name = :name";
+  $uid = db_query($sql,array(':name' => $username))->fetchField();
+
+  // Stripaluota.
+  tripal_set_user_quota($uid, $size, $expiration);
+
+  // TODO: check to make sure that the quota was actually set, can we assume
+  // it will always work?
+
+  drupal_set_message(t('Custom quota set for the user: @username', array('@username' => $username)));
+  drupal_goto('admin/tripal/files/quota');
+}
+
+/**
+ * Edit an existing users' quota and/or expiration date
+ **/
+function tripal_admin_edit_quota_form($form, &$form_state, $uid) {
+
+  $quota = tripal_get_user_quota($uid);
+  $default_quota = $quota->custom_quota;
+  $default_expiration = $quota->custom_expiration;
+
+  if (array_key_exists('values', $form_state)) {
+    $default_quota = $form_state['values']['default_quota'];
+    $default_expiration = $form_state['values']['default_expiration_date'];
+  }
+
+  $user = user_load($uid);
+
+  $form['uid'] = array(
+    '#type' => 'value',
+    '#value' => $uid
+  );
+
+  // Textfield (ajax call based off of existing users) for users on the site
+  $form['username'] = array (
+    '#type' => 'item',
+    '#title' => 'User',
+    '#markup' => $user->name,
+  );
+
+  // Custom quota textfield (prepopulated with defualt value)
+  $form['quota'] = array (
+    '#type' => 'textfield',
+    '#title' => 'Custom User Quota',
+    '#description' => 'Set the number of megabytes that a user can consume. The number must be followed by the suffix "MB" (megabytes) or "GB" (gigabytes) with no space between the number and the suffix (e.g.: 200MB).',
+    '#default_value' => tripal_format_bytes($default_quota),
+  );
+
+  // Custom exp date textfield (prepopulated with defualt value)
+  $form['expiration'] = array (
+    '#type' => 'textfield',
+    '#title' => 'Days to Expire',
+    '#description' => 'The number of days that a user uploaded file can remain on the server before it is automatically removed.',
+    '#default_value' => $default_expiration,
+  );
+
+  // Submit button
+  $form['button'] = array (
+    '#type' => 'submit',
+    '#value' => t('Submit'),
+  );
+  $form['cancel'] = array(
+    '#type' => 'markup',
+    '#markup' => l('Cancel', 'admin/tripal/files/quota'),
+  );
+
+  return $form;
+}
+
+/**
+ * Same validate as the add user with the exception of no duplicate entry
+ **/
+function tripal_admin_edit_quota_form_validate($form, &$form_state){
+
+  $uid = $form_state['values']['uid'];
+  $quota = $form_state['values']['quota'];
+  $expiration = $form_state['values']['expiration'];
+
+  // Validate the quota string.
+  if (!preg_match("/^\d+(MB|GB|TB)$/", $quota)) {
+    form_set_error('quota', t('Please provide a quota size in the format indicated.'));
+  }
+
+  // Validate the expiration time.
+  if (!preg_match("/^\d+$/", $expiration)) {
+    form_set_error('expiration', t('Please providate a positive non-decimal numeric value for the days to expire'));
+  }
+}
+
+/**
+ * Same submit as the quota overwrite function
+ **/
+function tripal_admin_edit_quota_form_submit($form, &$form_state) {
+  $uid = $form_state['values']['uid'];
+  $quota = $form_state['values']['quota'];
+  $expiration = $form_state['values']['expiration'];
+
+  // if the 2nd element of the qutoa string occupied by a valid suffix we need to check to see
+  // what we have to multiply the value by (1024 for GB 1024^2 for TB because
+  // we assume that the initial number is already in MB)
+  $matches = array();
+  $multiplier = 'MB';
+  $size = $quota;
+  if (preg_match("/^\d+(\.\d+)*(MB|GB|TB)$/", $quota, $matches)) {
+    $multiplier = $matches[2];
+    $size = $matches[1];
+  }
+
+  switch ($multiplier) {
+    case 'GB':
+      $size = (int) $quota * pow(10,9);
+      break;
+    case 'TB':
+      $size = (int) $quota * pow(10,12);
+      break;
+    default:
+      $size = (int) $quota * pow(10,6);
+      break;
+  }
+
+  // Set the user quota.
+  tripal_remove_user_quota($uid);
+  tripal_set_user_quota($uid, $size, $expiration);
+
+  $user = user_load($uid);
+
+  drupal_set_message(t('Custom quota set for the user: @username', array('@username' => $user->name)));
+  drupal_goto('admin/tripal/files/quota');
+}
+
+/**
+ * Implements the form for setting the default file settings.
+ */
+function tripal_admin_manage_files_form($form, &$form_state) {
+  
+  if (array_key_exists('values', $form_state)) {
+    $upload_max = $form_state['values']['upload_max'];
+  }
+  else {
+    $upload_max = tripal_format_bytes(variable_get('tripal_upload_max_size', 10000000000));
+  }
+  
+  $form['php_defaults'] = [
+    '#type' => 'item',
+    '#title' => 'PHP Maximum Upload Size',
+    '#description' => t('Your php.ini file is currently configured with this size as the maximum size allowed for a single file during upload. However, Tripal uses an HTML5 uploader that supports much larger file sizes.  It works by breaking the file into chunks and uploading each chunk separately. Therefore this becomes the maximum allowed size of a chunk.'),
+    '#markup' => ini_get("upload_max_filesize"),
+  ];
+  
+  $form['upload_max'] = [
+    '#type' => 'textfield',
+    '#title' => 'Maximum file size',
+    '#description' => t('Set the maximum size that a file can have for upload. The number must be followed by the suffix "MB" (megabytes) or "GB" (gigabytes) with no space between the number and the suffix (e.g.: 200MB).  No user will be allowed to upload a file larger than this when Tripal\'s file upload tool is used.'),
+    '#default_value' => $upload_max,
+  ];
+  
+  $form['update_defaults'] = [
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  ];
+  return $form;
+}
+
+/**
+ * Validate the form's values: proper numbers and/or MB, GB, TB for quota field.
+ **/
+function tripal_admin_manage_files_form_validate($form, &$form_state) {
+  $upload_max = $form_state['values']['upload_max'];
+  
+  // Validate the quota string.
+  if (!preg_match("/^\d+(\.\d+)*(MB|GB|TB)$/", $upload_max)) {
+    form_set_error('upload_max', t('Please provide a maximum size in the format indicated.'));
+  }
+}
+/**
+ * Implements the submit function of the tripal_admin_manage_files_form.
+ **/
+function tripal_admin_manage_files_form_submit($form, &$form_state) {
+  $upload_max = $form_state['values']['upload_max'];
+    
+  // if the 2nd element of the qutoa string occupied by a valid suffix we need to check to see
+  // what we have to multiply the value by (1024 for GB 1024^2 for TB because
+  // we assume that the initial number is already in MB)
+  $matches = array();
+  $multiplier = 'MB';
+  $size = $upload_max;
+  if (preg_match("/^(\d+(?:\.\d+)*)(MB|GB|TB)$/", $upload_max, $matches)) {
+    $multiplier = $matches[2];
+    $size = $matches[1];
+  }
+  
+  switch ($multiplier) {
+    case 'GB':
+      $size = (int) ($size * pow(10,9));
+      break;
+    case 'TB':
+      $size = (int) ($size * pow(10,12));
+      break;
+    default:
+      $size = (int) ($size * pow(10,6));
+      break;
+  }
+  variable_set('tripal_upload_max_size', $size);
+    
+  drupal_set_message('Default settings have been set.');
+}
+
+/**
+ * Provides the means of setting quotas and seeing server consumption.
+ *
+ * @return A table of the current users with custom quotas, fieldsets for
+ *         adding new users to the custom quotas, and fieldsets for setting
+ *         site wide quota and expiration date.
+ */
+function tripal_admin_manage_quota_form($form, &$form_state) {
+  // Provide overall server consumption (and space remaining)
+  $total_size = 0;
+  
+  $default_quota = variable_get('tripal_default_file_quota', pow(20,6));
+  $default_expiration = variable_get('tripal_default_file_expiration', '60');
+
+  if (array_key_exists('values', $form_state)) {
+    $default_quota = $form_state['values']['default_quota'];
+    $default_expiration = $form_state['values']['default_expiration_date'];
+  }
+
+  // Query file usage table for the fids that the module uses
+  // Iterate through all of the files managed by the tripal module
+  // and calculate the total.
+  $sql = "SELECT DISTINCT fid FROM {file_usage} WHERE module = 'tripal'";
+  $fids = db_query($sql);
+  while($fid = $fids->fetchObject()) {
+    $sql = "SELECT filesize FROM {file_managed} WHERE fid = :fid";
+    $total_size += db_query($sql, array (':fid' => $fid->fid))->fetchObject()->filesize;
+  }
+  $form['total_size'] = array (
+    '#type' => 'item',
+    '#title' => t('Total Current Usage'),
+    '#description' => t('The total amount of space consumed by user file uploads.'),
+    '#markup' => tripal_format_bytes($total_size),
+  );
+
+  // TODO: add a D3 chart showing the amount of storage used by each user.
+
+  $form['default_quota'] = array (
+    '#type' => 'textfield',
+    '#title' => 'Default System-Wide User Quota',
+    '#description' => t('Set the number of megabytes that a user can consume. The number must be followed by the suffix "MB" (megabytes) or "GB" (gigabytes) with no space between the number and the suffix (e.g.: 200MB).'),
+    '#default_value' => tripal_format_bytes($default_quota),
+  );
+
+  $form['default_expiration_date'] = array (
+    '#type' => 'textfield',
+    '#title' => 'Default System-Wide Expiration Date',
+    '#description' => t('The number of days that a user uploaded file can remain on the server before it is automatically removed'),
+    '#default_value' => $default_expiration,
+  );
+
+  // Populate the table from the custom quota db table (users, quota, exp date).
+  $header = array(
+    'uid' => t('UID'),
+    'user' => t('Users'),
+    'custom_quota' => t('Custom Quota'),
+    'exp_date' => t('Expiration Date'),
+    'actions' => t('Actions'),
+  );
+
+  // API call to the gather the users that have a custom quota
+  $rows = array();
+  $query = "SELECT * FROM {tripal_custom_quota}";
+  $data = db_query($query);
+  while($entry = $data->fetchObject()) {
+    $user = user_load($entry->uid);
+    $rows[] = array(
+      'uid' => $user->uid,
+      'user' => $user->name,
+      'custom_quota' => tripal_format_bytes($entry->custom_quota),
+      'exp_date' => $entry->custom_expiration,
+    );
+  }
+
+  // Add the Actions links
+  foreach($rows as $key => $entry){
+    $rows[$key]['actions'] = l('Edit', 'admin/tripal/files/quota/edit/' . $entry['uid']) . ' | ' .
+      l('Remove', 'admin/tripal/files/quota/remove/' . $entry['uid']);
+  }
+
+  $form['custom'] = array(
+    '#type' => 'fieldset',
+    '#title' => 'Custom Settings',
+    '#description' => t('The settings above apply to all users.  The following allows for custom user settings that override the defaults set above.'),
+    '#collapsed' => TRUE,
+    '#collapsible' => FALSE,
+  );
+
+  $form['custom']['links'] = array(
+    '#type' => 'markup',
+    '#markup' => '<br>' . l('Add Custom User Quota', 'admin/tripal/files/quota/add'),
+  );
+
+  $form['custom']['custom_quotas'] = array(
+    '#type' => 'item',
+    '#title' => t('Custom User Quotas'),
+
+    '#markup' => theme_table(array(
+      'header' => $header,
+      'rows' => $rows,
+      'attributes' => array(),
+      'caption' => '',
+      'sticky' => TRUE,
+      'empty' => 'There are no custom user quotas.',
+      'colgroups' => array(),
+    )),
+  );
+
+  $form['update_defaults'] = array (
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+
+  return $form;
+}
+
+
+/**
+ * Validate the form's values: proper numbers and/or MB, GB, TB for quota field.
+ **/
+function tripal_admin_manage_quota_form_validate($form, &$form_state) {
+  $quota = $form_state['values']['default_quota'];
+  $expiration = $form_state['values']['default_expiration_date'];
+
+  // Validate the quota string.
+  if (!preg_match("/^\d+(\.\d+)*(MB|GB|TB)$/", $quota)) {
+    form_set_error('default_quota', t('Please provide a quota size in the format indicated.'));
+  }
+
+  // Validate the expiration time.
+  if (!preg_match("/^\d+$/", $expiration)) {
+    form_set_error('default_expiration', t('Please providate a positive non-decimal numeric value for the days to expire'));
+  }
+
+}
+
+/**
+ * Write to the two drupal variables the site wide defualt quota and exp date.
+ **/
+function tripal_admin_manage_quota_form_submit($form, &$form_state) {
+  $quota = $form_state['values']['default_quota'];
+  $expiration = $form_state['values']['default_expiration_date'];
+
+  // if the 2nd element of the qutoa string occupied by a valid suffix we need to check to see
+  // what we have to multiply the value by (1024 for GB 1024^2 for TB because
+  // we assume that the initial number is already in MB)
+  $matches = array();
+  $multiplier = 'MB';
+  $size = $quota;
+  if (preg_match("/^(\d+(?:\.\d+)*)(MB|GB|TB)$/", $quota, $matches)) {
+    $multiplier = $matches[2];
+    $size = $matches[1];
+  }
+
+  switch ($multiplier) {
+    case 'GB':
+      $size = (int) ($size * pow(10,9));
+      break;
+    case 'TB':
+      $size = (int) ($size * pow(10,12));
+      break;
+    default:
+      $size = (int) ($size * pow(10,6));
+      break;
+  }
+
+  // Grab the quota value and exp_date to write to the drupal variables
+  variable_set('tripal_default_file_quota', $size);
+  variable_set('tripal_default_file_expiration', $expiration);
+
+  drupal_set_message('Default quota settings have been set.');
+}
+
+/**
+ * API call to remove a user from the custom quota table
+ *
+ * @return Boolean value to confirm removal from the table.
+ **/
+function tripal_admin_remove_quota_form($form, &$form_state, $uid) {
+  $form = array();
+
+  $form['uid'] = array(
+    '#type' => 'value',
+    '#value' => $uid,
+  );
+
+  $user = user_load($uid);
+  if (!$user) {
+    drupal_set_message('There was a problem finding this user\'s account. Could not remove the quota', 'error');
+    drupal_goto('admin/tripal/files/quota');
+    return $form;
+  }
+
+  return confirm_form($form,
+    t('Confirm removal of the custom quota for the user: "' . $user->name . '"?'),
+    'admin/tripal/files/quota',
+    t('Removal of the custom quota will enforce default quotas for the user. If the user\'s current usage exceeds the defaults the user must then remove files before more may be uploaded.')
+  );
+}
+
+/**
+ * Implements submit hook for the tripal_admin_remove_quota_form form.
+ */
+function tripal_admin_remove_quota_form_submit($form, &$form_state) {
+  $uid = $form_state['values']['uid'];
+  tripal_remove_user_quota($uid);
+
+  $user = user_load($uid);
+  drupal_set_message('The custom quota for user, "' . $user->name . '", has been removed.');
+
+  drupal_goto('admin/tripal/files/quota');
+}
+
+/**
+ * Provides contents for the File Usgae page.
+ */
+function tripal_admin_file_usage_page() {
+  // set the breadcrumb
+  $breadcrumb = array();
+  $breadcrumb[] = l('Home', '<front>');
+  $breadcrumb[] = l('Administration', 'admin');
+  $breadcrumb[] = l('Tripal', 'admin/tripal');
+  $breadcrumb[] = l('User File Management', 'admin/tripal/files');
+  drupal_set_breadcrumb($breadcrumb);
+  
+  $content = [
+    [
+      '#type' => 'markup',
+      '#markup' => 'Usage reports coming in the future...',
+    ],
+  ];
+  return $content;
+}

+ 33 - 7
tripal/includes/tripal.importer.inc

@@ -5,6 +5,7 @@
  * Build the form for a TripalImporter implementation.
  */
 function tripal_get_importer_form($form, &$form_state, $class) {
+  global $user;
 
   tripal_load_include_importer_class($class);
 
@@ -24,6 +25,20 @@ function tripal_get_importer_form($form, &$form_state, $class) {
   }
 
   if (array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) {
+    
+    $existing_files = tripal_get_user_uploads($user->uid, $class::$file_types);
+    if (count($existing_files) > 0) {
+      $fids = [0 => '--Select a file--'];
+      foreach ($existing_files as $fid => $file) {
+        $fids[$fid] = $file->filename . ' (' . tripal_format_bytes($file->filesize) . ') ';
+      }
+      $form['file']['file_upload_existing'] = [
+        '#type' => 'select',
+        '#title' => t('Existing Files'),
+        '#description' => t('You may select a file that is already uploaded.'),
+        '#options' => $fids,
+      ];
+    }
     $form['file']['file_upload']= array(
       '#type' => 'html5_file',
       '#title' => '',
@@ -102,7 +117,8 @@ function tripal_get_importer_form_validate($form, &$form_state) {
   $file_local = NULL;
   $file_upload = NULL;
   $file_remote = NULL;
-
+  $file_existing = NULL;
+  
   // Get the form values for the file.
   if (array_key_exists('file_local', $class::$methods) and $class::$methods['file_local'] == TRUE) {
     $file_local = trim($form_state['values']['file_local']);
@@ -123,15 +139,16 @@ function tripal_get_importer_form_validate($form, &$form_state) {
   }
   if (array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) {
     $file_upload = trim($form_state['values']['file_upload']);
+    if (array_key_exists('file_upload_existing', $form_state['values']) and $form_state['values']['file_upload_existing']) {
+      $file_existing = $form_state['values']['file_upload_existing'];
+    }
   }
   if (array_key_exists('file_remote', $class::$methods) and $class::$methods['file_remote'] == TRUE) {
     $file_remote = trim($form_state['values']['file_remote']);
   }
 
-
-
   // The user must provide at least an uploaded file or a local file path.
-  if ($class::$file_required == TRUE and !$file_upload and !$file_local and !$file_remote) {
+  if ($class::$file_required == TRUE and !$file_upload and !$file_local and !$file_remote and !$file_existing) {
     form_set_error('file_local', t("You must provide a file."));
   }
 
@@ -154,6 +171,7 @@ function tripal_get_importer_form_submit($form, &$form_state) {
   // full file path and the fid instead.
   unset($run_args['file_local']);
   unset($run_args['file_upload']);
+  unset($run_args['file_upload_existing']);
   unset($run_args['form_build_id']);
   unset($run_args['form_token']);
   unset($run_args['form_id']);
@@ -163,6 +181,7 @@ function tripal_get_importer_form_submit($form, &$form_state) {
   $file_local = NULL;
   $file_upload = NULL;
   $file_remote = NULL;
+  $file_existing = NULL;
 
   // Get the form values for the file.
   if (array_key_exists('file_local', $class::$methods) and $class::$methods['file_local'] == TRUE) {
@@ -170,23 +189,30 @@ function tripal_get_importer_form_submit($form, &$form_state) {
   }
   if (array_key_exists('file_upload', $class::$methods) and $class::$methods['file_upload'] == TRUE) {
     $file_upload = trim($form_state['values']['file_upload']);
+    if (array_key_exists('file_upload_existing', $form_state['values']) and $form_state['values']['file_upload_existing']) {
+      $file_existing = trim($form_state['values']['file_upload_existing']);
+    }
   }
   if (array_key_exists('file_remote', $class::$methods) and $class::$methods['file_remote'] == TRUE) {
     $file_remote = trim($form_state['values']['file_remote']);
   }
 
+
   // Sumbit a job for this loader.
   $fname = '';
   $fid = NULL;
   $file_details = array();
-  if ($file_local) {
+  if ($file_existing) {
+    $file_details['fid'] = $file_existing;
+  }
+  elseif ($file_local) {
     $fname = preg_replace("/.*\/(.*)/", "$1", $file_local);
     $file_details['file_local'] = $file_local;
   }
-  if ($file_upload) {
+  elseif ($file_upload) {
     $file_details['fid'] = $file_upload;
   }
-  if ($file_remote) {
+  elseif ($file_remote) {
     $file_details['file_remote'] = $file_remote;
   }
   try {

+ 81 - 25
tripal/includes/tripal.upload.inc

@@ -19,6 +19,38 @@ function tripal_file_upload($type, $filename, $action = NULL, $chunk = 0) {
     ));
     return;
   }
+  
+  // Make sure we don't go over the user's quota, but only do this check
+  // before loading the first chunk so we don't repeat it over and over again.
+  if ($action == 'check' and $chunk == 0) {
+    $usage = tripal_get_user_usage($user->uid);
+    $quota = tripal_get_user_quota($user->uid);
+    $quota_size = $quota->custom_quota;
+    if ($file_size + $usage > $quota_size) {
+      drupal_json_output(array(
+        'status' => 'failed',
+        'message' => t("Unfortunately, you can not upload this file as the size exceeds the remainder of your quota. See your account page under the 'Uploads' tab to manage your uploaded files."),
+        'file_id' => '',
+      ));
+      return;
+    }
+    
+    // Make sure we don't go over the max file upload size.
+    $upload_max = variable_get('tripal_upload_max_size', 10000000000);
+    if ($file_size > $upload_max) {
+      $message = t("Unfortunately, you can not upload this file as the size exceeds the the maximum file size allowed by this site: " . tripal_format_bytes($upload_max) . '. ');
+      
+      if (user_access('administer tripal')) {
+        $message .= t('You can manage the file upload by visiting: Home » Administration » Tripal » User File Management.');
+      }
+      drupal_json_output(array(
+        'status' => 'failed',
+        'message' => $message,
+        'file_id' => '',
+      ));
+      return;
+    }
+  }
 
   // Allow the module that will own the file to make some checks. The module
   // is allowed to stop the upload as needed.
@@ -42,7 +74,7 @@ function tripal_file_upload($type, $filename, $action = NULL, $chunk = 0) {
   }
 
   switch ($action) {
-    // If the action is 'put' then the callee is sending a chunk of the file
+    // If the action is 'save' then the callee is sending a chunk of the file
     case 'save':
       tripal_file_upload_put($filename, $chunk, $user_dir);
       break;
@@ -56,7 +88,6 @@ function tripal_file_upload($type, $filename, $action = NULL, $chunk = 0) {
 }
 /**
  * Merges all chunks into a single file
- * @param unknown $filename
  */
 function tripal_file_upload_merge($filename, $type, $user_dir) {
   global $user;
@@ -127,33 +158,58 @@ function tripal_file_upload_merge($filename, $type, $user_dir) {
     $status = 'failed';
     $message = 'Merge file is missing after upload ' . $merge_file . '.';
   }
+  
   $file_id = NULL;
 
-  // If the file has been successfully merged then let the calling module
-  // deal with it.
-  if ($status != 'failed') {
-    $function = $module . '_handle_uploaded_file';
-    if(function_exists($function)) {
-      $file_id = $function($filename, $merge_file, $type);
-      if ($file_id) {
-        $file = file_load($file_id);
-        $status = 'completed';
-        $full_path = drupal_realpath($file->uri);
-        $md5sum = md5_file($full_path);
-        $md5sum_file = fopen("$full_path.md5", "w");
-        fwrite($md5sum_file, $md5sum);
-        fclose($md5sum_file);
-        unlink($temp_dir);
-      }
-      else {
-        $status = 'failed';
-        $message = 'Could not add file to the database.';
-      }
+  // If the file has been successfully merged then do a few other things...
+  if ($status != 'failed') {   
+    
+    // See if this file is already managed if so, then it has been uploaded
+    // before and we don't need to add a managed item again.
+    $fid = db_select('file_managed', 'fm')
+      ->fields('fm', ['fid'])
+      ->condition('uri', $merge_file)
+      ->execute()
+      ->fetchField();
+    
+    // Add the file if it is not already managed.
+    if (!$fid) {
+      $file = new stdClass();
+      $file->uri = $merge_file;
+      $file->filename = $filename;
+      $file->filemime = file_get_mimetype($merge_file);
+      $file->uid = $user->uid;
+      $file->status = FILE_STATUS_PERMANENT;
+      $file = file_save($file);
+      $fid = $file->fid;
     }
-    else {
-      $status = 'failed';
-      $message = 'Cannot find the function: ' . $function . '().';
+    
+    // Reload the file object to get a full object.
+    $file_id = $fid;
+    $file = file_load($fid);
+    
+    // Set the file as being managed by Tripal.
+    file_usage_add($file, 'tripal', $type, 0);
+    
+    // Set the file expiration.
+    tripal_reset_file_expiration($fid);
+    
+    // Generate an md5 file the uploaded file.
+    $full_path = drupal_realpath($file->uri);
+    $md5sum = md5_file($full_path);
+    $md5sum_file = fopen("$full_path.md5", "w");
+    fwrite($md5sum_file, $md5sum);
+    fclose($md5sum_file);
+    
+    // Remove the temporary directory.
+    file_unmanaged_delete_recursive($temp_dir);
+    
+    // Now let the submitting module deal with it.
+    $function = $module . '_handle_uploaded_file';
+    if (function_exists($function)) {
+      $function($file, $type);
     }
+    $status = 'completed';
   }
 
   if ($status == 'failed') {

+ 328 - 0
tripal/includes/tripal.user.inc

@@ -0,0 +1,328 @@
+<?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
+     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'
+     GROUP BY FM.fid, TGEF.expiration_date
+     ORDER BY FM.filename
+   ";
+   $files = db_query($sql, array(':user_id' => $uid));
+   $rows = array();
+   While ($entry = $files->fetchObject()) {
+    $file = file_load($entry->fid);
+
+    // Don't list files that don't exist on the file system.
+    if (!file_exists($file->uri)) {
+      continue;
+    }
+
+    $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");
+
+    $rows[] = array(
+      $entry->fid,
+      l($file->filename,"/user/$uid/files/$file->fid"),
+      $date_uploaded,
+      $expiration,
+      tripal_format_bytes($file->filesize),
+      $actions,
+    );
+  }
+  $header = array('ID', 'File Name', 'Upload Date', 'Expiration', 'Size', 'Actions');
+
+  // Get the user quota settings.
+  $quota = tripal_get_user_quota($uid);
+  $usage = tripal_get_user_usage($uid);
+
+  $content = array(
+    'page_title' => array(
+      '#type' => 'markup',
+      '#markup' => '<h2>Your Uploaded 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>',
+    ),
+    'usage' => array(
+      '#type' => 'item',
+      '#title' => 'Current Usage',
+      '#markup' => tripal_format_bytes($usage),
+      '#description' => t('The total number of bytes you currently consume.'),
+    ),
+    'quota' => array(
+      '#type' => 'item',
+      '#title' => 'Current Quota',
+      '#markup' => tripal_format_bytes($quota->custom_quota),
+      '#description' => t('The maximum number of bytes of files you can upload.')
+    ),
+    'expiration' => array(
+      '#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.')
+    ),
+    '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.',
+      )),
+    )
+  );
+
+
+  if ($usage < $quota->custom_quota) {
+    drupal_set_message('Your file usage is currently below the file quota limit.');
+  }
+  else {
+    drupal_set_message('Your file usage is currently over your file quota limit. Please remove some files before uploading more', 'warning');
+  }
+
+  return $content;
+}
+
+/**
+ * User action to renew the expiration of a file.
+ *
+ * Adds the current time and the expiration date (either from default or if
+ * the user has a custom expiration date) to tripal_expiration_files
+ * table.
+ *
+ **/
+function tripal_renew_file($fid) {
+  $file = file_load($fid);
+  $success = tripal_reset_file_expiration($fid);
+
+  if ($success) {
+    drupal_set_message('Successfully updated expiration date.');
+  }
+  drupal_goto('user/' . $file->uid . '/files/');
+}
+
+/**
+ * Downloads a file.
+ *
+ * @param $fid
+ *   The File ID of the file to be downloaded.
+ */
+function tripal_download_file($fid) {
+  $file = file_load($fid);
+  if (file_exists($file->uri)) {
+    $headers = array();
+    $headers['Content-Type'] = $file->filemime;
+    $headers['Content-Disposition']  = 'attachment; filename=' . $file->filename;
+    $headers['Content-Length'] = $file->filesize;
+    file_transfer($file->uri, $headers);
+  }
+  else {
+    drupal_set_message('Can not download. The file no longer exists on the server.', 'error');
+    drupal_goto('user/' . $file->uid . '/files/');
+  }
+}
+
+/**
+ * Provides a confirmation form for deleting a galaxy workflow uploaded file.
+ */
+function tripal_delete_file_form($form, $form_state, $uid, $fid) {
+  $form = array();
+
+  $file = file_load($fid);
+
+  $form['uid'] = array(
+    '#type' => 'value',
+    '#value' => $uid,
+  );
+  $form['fid'] = array(
+    '#type' => 'value',
+    '#value' => $fid,
+  );
+
+  return confirm_form($form,
+      t('Confirm deletion of the file named "' . $file->filename . '"?'),
+      'user/' . $uid . '/files/',
+      t('Warning.  If this file is intended to be used with a analysis workflow submission that has not yet started then the workflow will fail. Once deleted, the file can no longer be used for new workflow submissions without uploading again.')
+  );
+}
+
+/**
+ * Implements a form submit for deleting a galaxy workflow uploaded file.
+ */
+function tripal_delete_file_form_submit($form, &$form_state) {
+  $fid = $form_state['values']['fid'];
+  $uid = $form_state['values']['uid'];
+  $file = file_load($fid);
+
+  // Remove the file from the file_usage table for all entries that link
+  // to the tripal module.
+  file_usage_delete($file, 'tripal', NULL, NULL, 0);
+
+  // Get any remaining usage for other modules
+  $file_usage = file_usage_list($file);
+
+  // If this file is still used by the tripal module then something
+  // didn't work right.
+  if (in_array('tripal', $file_usage)) {
+    drupal_set_message('The file could not be removed.  Please contact the site administrator.', 'error');
+  }
+
+  // If there is no other usage of this file from other modules then delete it.
+  if (count(array_keys($file_usage)) == 0) {
+    if (file_unmanaged_delete($file->uri)) {
+
+      // Also remove the md5 checksum.
+      if (file_exists(file_unmanaged_delete($file->uri . '.md5'))) {
+        file_unmanaged_delete($file->uri . '.md5');
+      }
+      drupal_set_message('The file has been fully removed.');
+    }
+    else {
+      drupal_set_message('The file has removed from this list and does not count against your quota, but other components of this site rely on this file. Thus it has not been fully removed.');
+    }
+  }
+  drupal_goto('user/' . $file->uid . '/files/');
+}
+
+/**
+ * Provides details about a file.
+ */
+function tripal_view_file($uid, $fid) {
+  $file = file_load($fid);
+
+  $headers = array();
+  $rows = array();
+
+  $actions = l('Delete', "user/$uid/files/$file->fid/delete") . '<br>' .
+             l('Download', "user/$uid/files/$file->fid/download");
+
+  // Name row
+  $rows[] = array(
+    array(
+      'data' => 'File Name',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $file->filename
+  );
+
+  $date_uploaded = date('Y-m-d H:i:s', $file->timestamp);
+  $rows[] = array(
+    array(
+      'data' => 'Upload Date',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $date_uploaded
+  );
+
+  $expiration_date = db_select('tripal_expiration_files', 'tgef')
+    ->fields('tgef', array('expiration_date'))
+    ->condition('fid', $fid)
+    ->execute()
+    ->fetchField();
+  $expiration = $expiration_date ? date('Y-m-d H:i:s', $expiration_date) : '';
+  $rows[] = array(
+    array(
+      'data' => 'Expiration Date',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $expiration
+  );
+
+  // Find which workflow submissions are using this file.
+  $usage = file_usage_list($file);
+  $usage = $usage['tripal'];
+  $workflow_nids = array();
+  foreach ($usage as $step => $nid) {
+    $nid = array_keys($nid)[0];
+    if (!in_array($nid, $workflow_nids)) {
+      $workflow_nids[] = $nid;
+    }
+  }
+  $wf_links = array();
+  foreach ($workflow_nids as $i => $nid) {
+    $query = db_select('tripal_workflow', 'tgw');
+    $query->fields('tgw', array('workflow_name'));
+    $query->innerJoin('tripal_workflow_submission', 'tgws', 'tgw.galaxy_workflow_id = tgws.galaxy_workflow_id');
+    $query->fields('tgws', array('sid'));
+    $query->condition('tgw.nid', $nid, '=');
+    $results = $query->execute();
+    $workflow = $results->fetchObject();
+    if ($workflow) {
+      $wf_links[] = l($workflow->workflow_name . ' (submission ID: ' . $workflow->sid . ')', "user/$uid/galaxy-jobs/$workflow->sid");
+    }
+  }
+  $rows[] = array(
+    array(
+      'data' => 'Usage',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    theme_item_list(array(
+      'items' => $wf_links,
+      'title' => '',
+      'type' => 'ul',
+      'attributes' => array(),
+    )),
+  );
+  $rows[] = array(
+    array(
+      'data' => 'Actions',
+      'header' => TRUE,
+      'width' => '20%',
+    ),
+    $actions
+  );
+
+  $content = array(
+    'description' => array(
+      '#type' => 'markup',
+      '#markup' => '<p>' . t('The following file has been uploaded for use in an analytical workflow.') . '</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;
+}
+
+

+ 84 - 0
tripal/tripal.install

@@ -154,6 +154,8 @@ function tripal_schema() {
   $schema['tripal_jobs'] = tripal_tripal_jobs_schema();
   $schema['tripal_token_formats'] = tripal_tripal_token_formats_schema();
   $schema['tripal_variables'] = tripal_tripal_variables_schema();
+  $schema['tripal_custom_quota'] = tripal_tripal_custom_quota_schema();
+  $schema['tripal_expiration_files'] = tripal_tripal_expiration_files_schema();
 
 
   // Adds a table for managing TripalEntity entities.
@@ -835,6 +837,71 @@ function tripal_tripal_bundle_variables_schema() {
   return $schema;
 }
 
+/**
+ * Provides the schema for the tripal_custom_quota table.
+ */
+function tripal_tripal_custom_quota_schema() {
+  $schema = array(
+    'table' => 'tripal_custom_quota',
+    'fields' => array(
+      'uid' => array(
+        'type' => 'int',
+        'size' => 'big',
+        'not NULL' => TRUE,
+      ),
+      'custom_quota' => array(
+        'type' => 'int',
+        'size' => 'big',
+        'not NULL' => TRUE,
+      ),
+      'custom_expiration' => array(
+        'type' => 'int',
+        'size' => 'big',
+        'not NULL' => TRUE,
+      ),
+    ),
+    'primary key' => array('uid'),
+    'unique keys' => array(
+      'tripal_custom_quota_uq1' => array('uid'),
+    ),
+    'indexes' => array(
+      'tripal_custom_quota_idx1' => array('uid'),
+    ),
+  );
+  return $schema;
+}
+
+/**
+ * Provides the schema for the tripal_expiration_files table.
+ */
+function tripal_tripal_expiration_files_schema() {
+  $schema = array (
+    'table' => 'tripal_expiration_files',
+    'fields' => array (
+      'fid' => array(
+        'type' => 'int',
+        'not NULL' => TRUE
+      ),
+      'expiration_date' => array(
+        'type' => 'int',
+        'size' => 'big',
+        'not NULL' => TRUE
+      ),
+    ),
+    'primary key' => array(
+      0 => 'fid'
+    ),
+    'unique keys' => array(
+      'tripal_expiration_files_uq1' => array('fid'),
+    ),
+    'indexes' => array(
+      'tripal_expiration_files_idx1' => array('fid'),
+    ),
+  );
+  return $schema;
+}
+
+
 /**
  * Additional Tripal Admin Notification Information.
  *
@@ -1096,3 +1163,20 @@ function tripal_update_7310() {
     throw new DrupalUpdateException('Could not add the tripal_collection_bundle table:' . $error);
   }
 }
+
+/**
+ * Adds support for file quotas.
+ */
+function tripal_update_7311() {
+  try {
+    $schema = array();
+    $schema['tripal_custom_quota'] = tripal_tripal_custom_quota_schema();
+    $schema['tripal_expiration_files'] = tripal_tripal_expiration_files_schema();
+    db_create_table('tripal_custom_quota', $schema['tripal_custom_quota']);
+    db_create_table('tripal_expiration_files', $schema['tripal_expiration_files']);
+  }
+  catch (\PDOException $e) {
+    $error = $e->getMessage();
+    throw new DrupalUpdateException('Could not perform update: '. $error);
+  }
+}

+ 233 - 61
tripal/tripal.module

@@ -424,10 +424,213 @@ function tripal_menu() {
     'file' => 'includes/tripal.admin.inc',
     'file path' => drupal_get_path('module', 'tripal'),
   );
-
+  
+  
+  //
+  // USER FILE MANAGEMENT
+  //
+  $items['admin/tripal/files'] = [
+    'title' => 'User File Management',
+    'description' => 'Set maximum upload sizes, quotas and view usage.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_manage_files_form'],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_NORMAL_ITEM,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+    'weight' => 30,
+  ];
+  
+  $items['admin/tripal/files/quota'] = [
+    'title' => 'User Quotas',
+    'description' => 'Set default quota, expiration date, and custom quotas',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_manage_quota_form'],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+    'weight' => 10,
+  ];
+  
+  // Admin remove user quota
+  $items['admin/tripal/files/quota/remove/%'] = [
+    'title' => 'Remove custom user quota',
+    'description' => "Revert's a user's quota and expiration of files to the site wide defaults.",
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_remove_quota_form', 5],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  
+  // Add user quota
+  $items['admin/tripal/files/quota/add'] = [
+    'title' => 'Add Custom Quota',
+    'description' => 'Gives the user a new quota and expiration date',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_add_custom_form'],
+    'access arguments' => ['administer tripal'],
+    // TODO: Ask Stephen is this is fine as a link at the top of the form
+    // as well as a link in the table
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  
+  // Autocomplete path for the users on the site
+  $items['admin/tripal/files/quota/user/autocomplete'] = [
+    'title' => 'Autocomplete for existing users',
+    'description' => 'Provide a list of existing users on the site.',
+    'page callback' => 'tripal_users_autocomplete',
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  
+  // Edit user quota
+  $items['admin/tripal/files/quota/edit/%'] = [
+    'title' => 'Edit Custom Quota',
+    'description' => 'Edit an existing user\'s quota and/or expiration date.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_edit_quota_form', 5],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  
+  $items['admin/tripal/files/usage'] = [
+    'title' => 'File Usage',
+    'description' => 'Set default quota, expiration date, and custom quotas',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => ['tripal_admin_file_usage_page'],
+    'access arguments' => ['administer tripal'],
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/tripal.admin_files.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+    'weight' => 15
+  ];
+
+  
+  //
+  // USER FILES
+  //
+  
+  // User view quota (Tab)
+  $items['user/%/files'] = [
+    'title' => 'Uploads',
+    '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 arguments' => ['view', 1],
+    'type' => MENU_LOCAL_TASK,
+    'file' => 'includes/tripal.user.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+    'weight' => 10,
+  ];
+  
+  $items['user/%/files/%'] = [
+    'title' => 'File Details',
+    'description' => "View details about the file",
+    'page callback' => 'tripal_view_file',
+    'page arguments' => [1, 3],
+    'access callback' => 'tripal_access_user_uploads',
+    '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 arguments' => ['renew', 1, 3],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.user.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  // User file download.
+  $items['user/%/files/%/download'] = [
+    'title' => 'Download File',
+    'description' => "Download a user's file based off of the link clicked in the table",
+    'page callback' => 'tripal_download_file',
+    'page arguments' => [3],
+    'access arguments' => ['download', 1, 3],
+    'access callback' => 'tripal_access_user_uploads',
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.user.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
+  // User file delete.
+  $items['user/%/files/%/delete'] = [
+    'title' => 'Delete File',
+    '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 arguments' => ['delete', 1, 3],
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/tripal.user.inc',
+    'file path' => drupal_get_path('module', 'tripal'),
+  ];
   return $items;
 }
 
+/**
+ * Checks if the current user has permissions to perform an action on a file.
+ *
+ * @param $op
+ *   The operation to perform.  These include 'view', 'download', 'renew' and 
+ *   'delete'
+ * @param $uid
+ *   The user ID of the user's account that owns the file.
+ * @param $fid
+ *   The file ID.
+ */
+function tripal_access_user_uploads($op, $uid, $fid = NULL) {
+  global $user;
+  
+  // The site admin can do anything.
+  if (in_array('administrator', $user->roles)) {
+    return TRUE;
+  }
+  
+  // Only the user that owns the files can see them.
+  if ($uid != $user->uid) {
+    return FALSE;
+  }
+  
+  // If no file ID is provided and the user wants to view then
+  // this is the case where the user wants to see all the files.
+  if (!$fid and $op == 'view') {
+    return TRUE;
+  }
+    
+  $file = file_load($fid);
+  switch ($op) {
+    case 'view':
+    case 'download':
+    case 'renew':
+    case 'delete':
+      if ($user->uid == $file->uid) {
+        return TRUE;
+      }
+      break;
+  }
+  return FALSE;
+}
+/**
+ * An access function for data collections.
+ * 
+ * @return boolean
+ */
 function tripal_accesss_user_collections($uid) {
   if (!tripal_access_user_data($uid)) {
     return FALSE;
@@ -439,6 +642,24 @@ function tripal_accesss_user_collections($uid) {
   return TRUE;
 }
 
+/**
+ * Autocomplete function for listing existing users on the site.
+ *
+ * @return json array of users that match the query in the textfield
+ **/
+function tripal_users_autocomplete($string) {
+  $matches = [];
+  $result = db_select('users', 'u')
+    ->fields('u', ['name'])
+    ->condition('name', '%' . db_like($string) . '%', 'LIKE')
+    ->execute();
+  
+  foreach ($result as $row) {
+    $matches[$row->name] = check_plain($row->name);
+  }
+  drupal_json_output($matches);
+}
+
 /**
  * Access callback for accessing a user's Tripal-added private data.
  *
@@ -770,6 +991,7 @@ function tripal_import_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.quotas.api');
   module_load_include('inc', 'tripal', 'api/tripal.DEPRECATED.api');
 }
 
@@ -1105,37 +1327,25 @@ function tripal_block_configure($delta = '') {
 function tripal_cron() {
 
   // Add jobs to the Tripal queue for commong tasks.
-  $args = array();
-  $includes = array();
+  $args = [];
+  $includes = [];
 
   if (variable_get('tripal_admin_notification_creation_during_cron', TRUE)) {
     $modules = module_implements('tripal_cron_notification');
     foreach ($modules as $module) {
       $function = $module . '_tripal_cron_notification';
-      tripal_add_job(
-        "Cron: Checking for '$module' notifications.",    // Job Name
-        'tripal',                                         // Module Name
-        $function,                                        // Callback
-        $args,                                            // Arguements
-        1,                                                // User UID
-        1,                                                // Priority (1-10)
-        $includes,                                        // Includes
-        TRUE                                              // Ignore Duplicates
-      );
+      tripal_add_job("Cron: Checking for '$module' notifications.", 'tripal',                                         // Module Name
+        $function, $args, 1, 1, $includes, TRUE);
     }
   }
 
   // Check for expired collections.
-  tripal_add_job(
-    'Cron: Checking expired collections',             // Job Name
-    'tripal',                                         // Module Name
-    'tripal_expire_collections',                      // Callback
-    $args,                                            // Arguements
-    1,                                                // User UID
-    1,                                                // Priority (1-10)
-    $includes,                                        // Includes
-    TRUE                                              // Ignore Duplicates
-  );
+  tripal_add_job('Cron: Checking expired collections', 'tripal',
+    'tripal_expire_collections', $args, 1, 1, $includes, TRUE);
+  
+  tripal_add_job('Cron: Checking expired files', 'tripal',
+    'tripal_expire_files', $args, 1, 1, $includes, TRUE);
+
 }
 
 /**
@@ -1310,44 +1520,6 @@ function tripal_html5_file_value($element, $input = FALSE, &$form_state) {
   }
 }
 
-
-/**
- * Implements hook_handle_uploaded_file().
- */
-function tripal_handle_uploaded_file($filename, $filepath, $type) {
-
-  global $user;
-
-  // Split the type into a node ID and form_key
-  list($id, $form_key) = explode('-', $type);
-
-
-  // See if this file is already managed then add another entry fin the
-  // usage table.
-  $fid = db_select('file_managed', 'fm')
-    ->fields('fm', array('fid'))
-    ->condition('uri', $filepath)
-    ->execute()
-    ->fetchField();
-
-  // Create a file object.
-  if (!$fid) {
-    $file = new stdClass();
-    $file->uri = $filepath;
-    $file->filename = $filename;
-    $file->filemime = file_get_mimetype($filepath);
-    $file->uid = $user->uid;
-    $file->status = FILE_STATUS_PERMANENT;
-    $file = file_save($file);
-    $fid = $file->fid;
-  }
-
-  $file = file_load($fid);
-  file_usage_add($file, 'tripal', $form_key, $id);
-  return $fid;
-
-}
-
 /**
  * Implements hook_field_display_alter().
  *