Prechádzať zdrojové kódy

Can now install modules on the Extension admin page

Stephen Ficklin 10 rokov pred
rodič
commit
4bf9345e71

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

@@ -188,7 +188,7 @@ function tripal_set_message($message, $importance = TRIPAL_INFO, $options = arra
   // Mark-up the Message
   $full_message =
      '<div class="tripal-site-admin-message">'
-       . '<span class="tripal-serverity-string ' . strtolower($importance_string) . '">' . $importance_string . ': </span>'
+       . '<span class="tripal-severity-string ' . strtolower($importance_string) . '">' . $importance_string . ': </span>'
        . $message
    . '</div>';
 

+ 567 - 78
tripal_core/includes/tripal_core.extensions.inc

@@ -1,66 +1,225 @@
 <?php
 
 function tripal_core_extensions_form($form, &$form_state = NULL) {
-  $tab = $_GET['tab'];
-
-  $form['extensions'] = array(
-    '#type' => 'vertical_tabs',
-    '#default_tab' => $tab,
-  );
-
-  // Get the RSS feed XML from the tripa.info website.
-  $content = file_get_contents("http://tripal.info/rss/extensions.xml");
+  // Get the RSS feed XML from the tripa.info website and save it to
+  // a temp file so that we don't have to keep pulling the XML
+  // everythime the page is loaded. If the temp file is older than 1 hours
+  // then we'll pull it again. The number of seconds in an hour is 3600
+  $tmp_file = sys_get_temp_dir() . '/tripal_rss_extensions.xml';
+  if (!file_exists($tmp_file) or time() - filemtime($tmp_file) > 3600) {
+    $content = file_get_contents("http://tripal.info/rss/extensions.xml");
+    file_put_contents($tmp_file, $content);
+  }
+  else {
+    $content = file_get_contents($tmp_file);
+  }
   $xml = new SimpleXmlElement($content);
   $namespace = "http://tripal.info/rss/extensions/";
 
-  // Parse the items into an array indexed by category and compatible versions.
-  $items = array();
+
+  $tab = array_key_exists('tab', $_GET) ? $_GET['tab'] : '';
+
+  // Get any filters by categories.
+  $filters = array();
+  if (array_key_exists('values', $form_state)) {
+    foreach ($form_state['values'] as $key => $value) {
+      // Get the category to be filtered on.
+      $matches = array();
+      if (preg_match('/^categories-(.*?)$/', $key, $matches)) {
+        if ($value == 'any') {
+          continue;
+        }
+        $filters[$matches[1]] = $value;
+      }
+    }
+  }
+
+  // Parse the items into an array indexed by type and compatible versions.
+  $extensions = array();
   $types = array();
+  $type_ids = array();
   $categories = array();
-  foreach ($xml->channel->item as $item) {
-    $type = (string) $item->category;
+  $cvs = array();
+  $tvs = array();
+  foreach ($xml->channel->item as $extension) {
+    // Get the type of extension, convert that into a machine-readable name,
+    // and build the array of types.
+    $type = (string) $extension->category;
+    $type_id = preg_replace('/[^\w]/','_', strtolower($type));
+    $type_ids[$type] = $type_id;
+    if (!in_array($type_id, $types)) {
+      $types[$type] = 1;
+    }
+
+    // Get the categories list for this item.
+    $cats = preg_split('/, /', (string) $extension->children($namespace)->categories);
+
+    // Get the guid for this extension
+    $guid = (string) $extension->guid;
+
     // In order to get fields in the 'tripal_extension' name space we must
     // pass in the $namespace to the children function.  We first get the
     // Tripal versions, then the chado versions and organize the elements
     // accordintly.
-    $tvs = preg_split('/, /', (string) $item->children($namespace)->tripal_version);
-    foreach($tvs as $tv) {
-      $cvs = preg_split('/, /', (string) $item->children($namespace)->chado_version);
-      foreach($cvs as $cv) {
-        // Index the items by category, tripal version and chado version
-        $items[$tv][$cv][$type][] = $item;
-        if (!in_array($type, $types)) {
-          $types[] = $type;
+    $tvs_temp = preg_split('/, /', (string) $extension->children($namespace)->tripal_version);
+    foreach($tvs_temp as $tv) {
+      $tvs[$tv] = 1;
+      $cvs_temp = preg_split('/, /', (string) $extension->children($namespace)->chado_version);
+      foreach($cvs_temp as $cv) {
+        $cvs[$cv] = 1;
+
+        // Keep track of the categories this item has been assigned.
+        foreach ($cats as $cat) {
+          $categories[$tv][$cv][$type][$cat] = 1;
+          $categories['any'][$cv][$type][$cat] = 1;
+          $categories[$tv]['any'][$type][$cat] = 1;
+          $categories['any']['any'][$type][$cat] = 1;
+        }
+
+        // If there are filters then only include extensions that match the filters.
+        if (array_key_exists($type_id, $filters) and !in_array($filters[$type_id], $cats)) {
+          continue;
         }
+
+        // Index the items by type, tripal version and chado version.
+        $item = array();
+        foreach ($extension->children() as $child) {
+          $item[$child->getName()] = (string) $child;
+        }
+        foreach ($extension->children($namespace) as $child) {
+          $item[$namespace][$child->getName()] = (string) $child;
+        }
+        $extensions[$tv][$cv][$type][$guid] = $item;
+        $extensions['any'][$cv][$type][$guid] = $item;
+        $extensions[$tv]['any'][$type][$guid] = $item;
+        $extensions['any']['any'][$type][$guid] = $item;
       }
     }
   }
 
+  // Convert the arrays from an associative array into a normal array, and sort.
+  $types = array_keys($types);
+  sort($types);
+  $cvs = array_keys($cvs);
+  sort($cvs);
+  $tvs = array_keys($tvs);
+  sort($tvs);
 
   // Get the Chado version and convert to the expected format
   $chado_version = chado_get_version(TRUE);
   $chado_version = preg_replace('/^(\d\.\d).*$/', "v$1x", $chado_version);
+  $my_chado_version = $chado_version;
+  // The default value can come from the pager links (thus via $_GET) or
+  // via ajax call (thus via $form_state).
+  if (array_key_exists('cv', $_GET)) {
+    $chado_version = $_GET['cv'];
+  }
+  if (array_key_exists('values', $form_state) and array_key_exists('cv', $form_state['values'])) {
+    $chado_version = $form_state['values']['cv'];
+  }
 
   // Get the Tripal version. This is the version set in the tripal_core.info
   $info = system_get_info('module', 'tripal_core');
   $tripal_version = $info['version'];
   $tripal_version = preg_replace('/^.*?-(\d\.\d+).*$/', "v$1", $tripal_version);
+  $my_tripal_version = $tripal_version;
+  if (array_key_exists('tv', $_GET)) {
+    $tripal_version = $_GET['tv'];
+  }
+  if (array_key_exists('values', $form_state) and array_key_exists('tv', $form_state['values'])) {
+    $tripal_version = $form_state['values']['tv'];
+  }
 
+  // Add the instructions.
   $form['instructions'] = array(
     '#type' => 'item',
     '#markup' => t('This page will help you find extensions that are available
-      for Tripal.  Select an extension type from the vertical tabs to see
-      the each type of extension.')
+      for Tripal.  Select an extension type from the vertical tabs. The content
+      of this page is constructed from an RSS feed provided by tripal.info.
+      There may be no content if the tripal.info site is unavailable. The RSS
+      feed will be cached for one hour.')
+  );
+  // Add the filters fieldset.
+  $form['filters'] = array(
+    '#type' => 'fieldset',
+    '#title' => 'Filters',
+    '#description' => t('You can filter which extensions are visible by
+      changing the Tripal ahd Chado versions. By default only those
+      extensions that are compatible with the currently installed Tripal
+      and Chado verions are shown.'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+  $summary_message = 'Currently showing extensions compatible with ';
+  if ($tripal_version != 'any' and $chado_version != 'any') {
+    $summary_message .= "<strong>Tripal $tripal_version</strong> and <strong>Chado $chado_version</strong>.";
+  }
+  elseif ($tripal_version == 'any' and $chado_version != 'any') {
+    $summary_message .= "<strong>any Tripal</strong> version and <strong>Chado $chado_version</strong>.";
+  }
+  elseif ($tripal_version != 'any' and $chado_version == 'any') {
+    $summary_message .= "<strong>Tripal $tripal_version</strong> and <strong>any Chado</strong> version.";
+  }
+  elseif ($tripal_version == 'any' and $chado_version == 'any') {
+    $summary_message .= "<strong>any Tripal</strong> version and <strong>any Chado</strong> version.";
+  }
+  $form['filter_summary'] = array(
+    '#type' => 'item',
+    '#markup' => $summary_message,
   );
 
-  // Build the fieldsets for each category
-  sort($types);
-  $type_ids = array();
+  // Add the Tripal version select box.
+  $options = array();
+  $options['any'] = '--Any--';
+  foreach ($tvs as $tv) {
+    $options[$tv] = $tv;
+  }
+  $form['filters']['tv'] = array(
+    '#type' => 'select',
+    '#title' => 'Tripal',
+    '#options' => $options,
+    '#default_value' => $tripal_version,
+    '#ajax'  => array(
+      'callback' => "tripal_core_extensions_form_ajax_callback",
+      'wrapper'  => 'tripal_core_extensions',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+    ),
+    '#prefix' => '<div style="float: left;">',
+    '#suffix' => '</div>'
+  );
+
+  // Add the Chado version select box.
+  $options = array();
+  $options['any'] = '--Any--';
+  foreach ($cvs as $cv) {
+    $options[$cv] = $cv;
+  }
+  $form['filters']['cv'] = array(
+    '#type' => 'select',
+    '#title' => 'Chado',
+    '#options' => $options,
+    '#default_value' => $chado_version,
+    '#ajax'  => array(
+      'callback' => "tripal_core_extensions_form_ajax_callback",
+      'wrapper'  => 'tripal_core_extensions',
+      'effect'   => 'fade',
+      'method'   => 'replace',
+    ),
+    '#prefix' => '<div style="float: left; padding-left: 10px">',
+    '#suffix' => '</div>'
+  );
+
+  // Add the vertical tabs
+  $form['extensions'] = array(
+    '#type' => 'vertical_tabs',
+    '#default_tab' => $tab,
+  );
+
+  // Add the fieldsets for each type
   foreach ($types as $type) {
-    $type_id = preg_replace('/[^\w]/','_', strtolower($type));
-    $type_ids[$type] = $type_id;
     $form[$type] = array(
-      '#id'          => $type_id,
+      '#id'          => $type_ids[$type],
       '#type'        => 'fieldset',
       '#title'       => $type . 's',
       '#collapsed'   => TRUE,
@@ -69,14 +228,82 @@ function tripal_core_extensions_form($form, &$form_state = NULL) {
     );
   }
 
-  // Iterate through the compatible extensions. We will add a pager for
+  // Iterate through all of the extensions and add them to the form.
+  tripal_core_extension_form_add_extensions($form, $form_state,
+    $extensions[$tripal_version][$chado_version], $categories, $tripal_version,
+    $chado_version, $my_tripal_version, $my_chado_version, $type_ids,
+    $namespace, $filters);
+
+  foreach ($types as $type) {
+    if (count(element_children($form[$type])) == 0) {
+      $form[$type]['empty'] = array(
+        '#type' => 'item',
+        '#markup' => '<strong>There are no matching ' . strtolower($type) . '(s).</strong>',
+      );
+    }
+  }
+
+  $form['#prefix'] = '<div id="tripal_core_extensions">';
+  $form['#suffix'] = '</div>';
+  $form['#submit'][] = 'tripal_core_extensions_form_submit';
+  return $form;
+}
+
+/**
+ * Adds each extension to the form.
+ *
+ * This function exits to simplify the the tripal_core_extension_form()
+ * function.
+ */
+function tripal_core_extension_form_add_extensions(&$form, $form_state, $extensions,
+  $categories, $tripal_version, $chado_version, $my_tripal_version,
+  $my_chado_version, $type_ids, $namespace, $filters) {
+
+  // Iterate through the extensions. We will add a pager for
   // each type of extension, and display only those that should appear
   // on the page.
   $type_index = 0;
-  foreach ($items[$tripal_version][$chado_version] as $type => $items) {
+  foreach ($extensions as $type => $extensions) {
+    $total_items = count($extensions);
+
+    // Indicate how many matching extensions were found
+    $form[$type]['total_found'] = array(
+      '#type' => 'item',
+      '#markup' => '<strong>Found ' . $total_items . ' matching ' . strtolower($type) . '(s)</strong>',
+    );
+
+    // Add the category select box;
+    $cats = array_keys($categories[$tripal_version][$chado_version][$type]);
+    sort($cats);
+    $options = array();
+    $options['any'] = '--Any--';
+    foreach ($cats as $cat) {
+      $options[$cat] = $cat;
+    }
+    // The default value can come from the pager links (thus via $_GET) or
+    // via ajax call (thus via $form_state).
+    $default_filter = '';
+    if (array_key_exists('categories-' . $type_ids[$type], $_GET)) {
+      $default_filter = $_GET['categories-' . $type_ids[$type]];
+    }
+    if (array_key_exists('values', $form_state) and array_key_exists('categories-' . $type_ids[$type], $form_state['values'])) {
+      $default_filter = $form_state['values']['categories-' . $type_ids[$type]];
+    }
+    $form[$type]['filters']['categories-' . $type_ids[$type]] = array(
+      '#type' => 'select',
+      '#title' => 'Filter by Category',
+      '#options' => $options,
+      '#default_value' => $default_filter,
+      '#ajax'  => array(
+        'callback' => "tripal_core_extensions_form_ajax_callback",
+        'wrapper'  => 'tripal_core_extensions',
+        'effect'   => 'fade',
+        'method'   => 'replace',
+      ),
+    );
+
     // Initialize pager and gets current page
     $num_per_page = 5;
-    $total_items = count($items);
     $page = pager_default_initialize($total_items, $num_per_page, $type_index);
 
     // Gets first record and last record to show
@@ -84,71 +311,155 @@ function tripal_core_extensions_form($form, &$form_state = NULL) {
     $end = ($start + $num_per_page < $total_items)? $start + $num_per_page : $total_items;
     // Iterate through each of the elements and add them to the form if
     // they are within the page
-    $item_index = 0;
-    foreach ($items as $item) {
+    $extension_index = 0;
+    foreach ($extensions as $guid => $extension) {
       // Skip items that aren't on our current page.
-      if ($item_index < $start or $item_index >= $end) {
-        $item_index++;
+      if ($extension_index < $start or $extension_index >= $end) {
+        $extension_index++;
         continue;
       }
-      // The unique guid is used to keep track of each extension in the form.
-      $guid = (string) $item->guid;
 
       // If this is an extension module then there will be a home page for it
       $home_page = '';
-      if ((string) $item->children($namespace)->home_page) {
-        $home_page = "<strong>Project Home: </strong>" . (string) $item->children($namespace)->home_page . "</br>";
+      if (array_key_exists('home_page', $extension[$namespace])) {
+        $home_page = "<strong>Project Home: </strong>" . $extension[$namespace]['home_page'] . "</br>";
+      }
+
+      // Determine if this extension is compatible with this site.
+      $incompatible = '';
+      $tvs_temp = preg_split('/, /', $extension[$namespace]['tripal_version']);
+      $cvs_temp = preg_split('/, /', $extension[$namespace]['chado_version']);
+      if (!in_array($my_tripal_version, $tvs_temp)) {
+        $incompatible .= "<li>This extensions is not compatible with this version of Tripal.</li>";
+      }
+      if (!in_array($my_chado_version, $cvs_temp)) {
+        $incompatible .= "<li>This extensions is not compatible with the installed Chado version.</li>";
+      }
+      $incompatible = t($incompatible);
+
+      // Determine if this extension is already installed.
+      $is_installed = '';
+      switch ($type) {
+        case 'Bulk Loader Template':
+          $blk_id = db_select('tripal_bulk_loader_template' ,'tblt')
+            ->fields('tblt', array('template_id'))
+            ->condition('name', $extension['title'])
+            ->execute()
+            ->fetchField();
+          if ($blk_id) {
+            $is_installed = '<li>A bulk loader tempalte with this name is already installed.</li>';
+          }
+          break;
+        case 'Materialized View':
+          $mview_id = tripal_get_mview_id($extension[$namespace]['mview_name']);
+          if ($mview_id) {
+            $is_installed = '<li>A materialized view with this name is already installed.</li>';
+          }
+          break;
+        case 'Extension Module':
+          if (module_exists($extension[$namespace]['module_name'])) {
+            $is_installed = '<li>A module with this name is already installed.</li>';
+          }
+          break;
+        default:
+          break;
+      }
+      $is_installed = t($is_installed);
+
+      // Does this module appear to be available on Drupal.org?
+      $project = '';
+      if ($type == 'Extension Module') {
+        // Does it have a drupal project name?
+        if (!array_key_exists('drupal_project', $extension[$namespace])) {
+          // Since it doesn't have a drupal project name is it in a sandbox?
+          if (!preg_match('/www.drupal.org\/sandbox/', $extension[$namespace]['home_page'])) {
+            $project = t("<li>This module does not appear to be available as a " .
+              "full Drupal project or within a Drupal sandbox and thus cannot ".
+              "be downloaded here. You may have to manually download it.</li>");
+          }
+          else {
+            $project = ("<li>This module is still in a Sandbox on Drupal.org, and
+              cannot be downloaded from here. You will have to manually download
+              this module.</li>");
+          }
+        }
+      }
+
+      // Make sure the bulk loader module is installed, or we cannot provide
+      // the bulk loader import button.
+      $other = '';
+      if ($type == 'Bulk Loader Template' and !module_exists('tripal_bulk_loader')) {
+        $other = t('<li>The bulk loader
+          module is not enabled. If you would like to import a loading template
+          please enable it.</li>');
       }
 
       // If the user click's the button to import the extension then we
       // need the item in the submit function so we can process the import.
-      $form[$type]['item-' . $guid] = array(
+      $form[$type]['extension-' . $guid] = array(
         '#type' => 'value',
-        '#value' => $item,
+        '#value' => $extension,
       );
 
+      $warnings = '';
+      if ($incompatible or $is_installed  or $project or $other) {
+        $warnings = '<div class="messages warning"><ul>' .
+          $incompatible . ' ' .
+          $is_installed . ' ' .
+          $project .  ' ' .
+          $other . '</ul></div>';
+      }
+
       // Create the form elements that we'll later theme into tables.
       $form[$type][$guid]['header'] = array(
-        '#markup' => l((string) $item->title, $item->link),
+        '#markup' => l($extension['title'], $extension['link']),
       );
       $form[$type][$guid]['details'] = array(
         '#markup' => "" .
-          "<strong>Type:</strong> " . $type . "</br>" .
-          "<strong>Categories: </strong>" . (string) $item->children($namespace)->categories . "</br>" .
-          "<strong>Authors: </strong>" . (string) $item->children($namespace)->authors . "</br>" .
-          "<strong>Chado compatible versions: </strong>" . (string) $item->children($namespace)->chado_version . "</br>" .
-          "<strong>Chado compatible versions: </strong>" . (string) $item->children($namespace)->tripal_version . "</br>" .
-          $home_page .
-          "<p>" . (string) $item->description . "</p>",
+        "<strong>Type:</strong> " . $type . "</br>" .
+        "<strong>Categories: </strong>" . $extension[$namespace]['categories'] . "</br>" .
+        "<strong>Authors: </strong>" . $extension[$namespace]['authors'] . "</br>" .
+        "<strong>Chado compatible versions: </strong>" . $extension[$namespace]['chado_version'] . "</br>" .
+        "<strong>Tripal compatible versions: </strong>" . $extension[$namespace]['tripal_version'] . "</br>" .
+        $home_page .
+        "<strong>tripal.info Page: </strong>" . l($extension['link'], $extension['link']) . "</br>" .
+        $warnings .
+        "<p>" . $extension['description'] . "</p>",
       );
       // Add an import button to each of types that can support import.
       switch ($type) {
         case 'Bulk Loader Template':
-          $form[$type][$guid]['import'] = array(
-            '#type' => 'submit',
-            '#value' => "Import Loader",
-            '#name' => "import-" . $guid,
-          );
+          if (!$incompatible and !$is_installed and !$project) {
+            $form[$type][$guid]['import'] = array(
+              '#type' => 'submit',
+              '#value' => "Import Loader",
+              '#name' => "import-" . $guid,
+            );
+          }
           break;
         case 'Materialized View':
-          $form[$type][$guid]['import'] = array(
-            '#type' => 'submit',
-            '#value' => "Import MView",
-            '#name' => "import-" . $guid,
-          );
+          if (!$incompatible and !$is_installed and !$project) {
+            $form[$type][$guid]['import'] = array(
+              '#type' => 'submit',
+              '#value' => "Import MView",
+              '#name' => "import-" . $guid,
+            );
+          }
           break;
         case 'Extension Module':
-          $form[$type][$guid]['import'] = array(
-            '#type' => 'submit',
-            '#value' => "Download Module",
-            '#name' => "import-" . $guid,
-          );
+          if (!$incompatible and !$is_installed and !$project) {
+            $form[$type][$guid]['import'] = array(
+              '#type' => 'submit',
+              '#value' => "Download Module",
+              '#name' => "import-" . $guid,
+            );
+          }
           break;
         default:
           break;
       }
       $form[$type][$guid]['#theme'] = 'tripal_core_extensions_form_tables';
-      $item_index++;
+      $extension_index++;
     }
 
     // Now create and theme the pager.
@@ -157,25 +468,31 @@ function tripal_core_extensions_form($form, &$form_state = NULL) {
       'element' => $type_index,
       'parameters' => array(
         'tab' => $type_ids[$type],
+        'cv' => $chado_version,
+        'tv' => $tripal_version,
       ),
       'quantity' => $num_per_page,
     );
 
+    // now add the category filters to the params array
+    foreach ($filters as $filter_id => $value) {
+      $pager['parameters']['categories-' . $filter_id] = $value;
+    }
+
     // because this may be an ajax callback, the theme_pager will set the URL to be
     // "system/ajax", so we need to reset that
+    $pager = theme('pager', $pager);
     global $base_path;
     $pager = str_replace($base_path . "system/ajax", "", $pager) ;
 
     $form[$type]['pager'] = array(
       '#type' => 'item',
-      '#markup' => theme('pager', $pager),
+      '#markup' => $pager,
     );
     $type_index++;
   }
-  return $form;
 }
 
-
 /**
  * Process the import buttons.
  *
@@ -188,16 +505,31 @@ function tripal_core_extensions_form_submit($form, &$form_state) {
   $guid = preg_replace('/^import-(\d+)$/', "$1", $clicked_button);
   if ($guid) {
     $namespace = "http://tripal.info/rss/extensions/";
-    $item = $form_state['values']['item-' . $guid];
-    $type = $item->category;
+    $extension = $form_state['values']['extension-' . $guid];
+    $type = $extension['category'];
     switch ($type) {
       case 'Bulk Loader Template':
+        // TODO: we need an API function for adding a bulk loader
+        $id = db_insert('tripal_bulk_loader_template')
+          ->fields(array(
+            'name' => $extension['title'],
+            'template_array' => $extension[$namespace]['bulkldr_export'],
+            'created' => time(),
+            'changed' => time()
+          ))
+          ->execute();
+        if (!$id) {
+          drupal_set_message("Cannot import this bulk loader. Please view the 'Recent log messages' report for more details.",  'error');
+        }
+        else {
+          drupal_set_message("Bulk loader succesfully added.");
+        }
         break;
       case 'Materialized View':
-        $modulename = 'tripal_core';
-        $mview_name = (string) $item->children($namespace)->mview_name;
-        $mview_schema = (string) $item->children($namespace)->mview_schema;
-        $mview_sql = (string) $item->children($namespace)->mview_sql;
+        $module_name = 'tripal_core';
+        $mview_name = $extension[$namespace]['mview_name'];
+        $mview_schema = $extension[$namespace]['mview_schema'];
+        $mview_sql = $extension[$namespace]['mview_sql'];
 
         // Validate the contents of the schema array.
         // TODO: security issue!! Before using 'eval' on this string
@@ -207,12 +539,75 @@ function tripal_core_extensions_form_submit($form, &$form_state) {
         $success = eval("\$schema_array = $mview_schema;");
         $error = chado_validate_custom_table_schema($schema_array);
         if ($error) {
-          drupal_set_message("Cannot import Materialized View.  $error");
+          drupal_set_message("Cannot import Materialized View: $error", "error");
         }
-        tripal_add_mview($mview_name, $modulename, $schema_array, $mview_sql);
+        tripal_add_mview($mview_name, $module_name, $schema_array, $mview_sql);
         break;
       case 'Extension Module':
+        if (array_key_exists('drupal_project', $extension[$namespace])) {
+          module_load_include('module', 'update', 'update');
+          module_load_include('inc', 'update', 'update.manager');
+          $project = $extension[$namespace]['drupal_project'];
+          $tar = tripal_core_extensions_get_latest_module_version($project);
+          if (!$tar) {
+            drupal_set_message('Cannot find a suitable release of this module
+              for download. You may need to manually install this module.', 'error');
+          }
+          else {
 
+            // Download the file from the Drupal repository
+            $local_cache = update_manager_file_get($tar);
+            if (!$local_cache) {
+              drupal_set_message('Cannot download the file. Check the
+                "Recent log messages" for relavent errors.', 'error');
+            }
+            else {
+              // The following code was borrowed from the update_manager_install_form_submit()
+              // of drupal in the modules/update/update.manager.inc file.
+              $directory = _update_manager_extract_directory();
+              try {
+                $archive = update_manager_archive_extract($local_cache, $directory);
+              }
+              catch (Exception $e) {
+                drupal_set_message('Cannot extract the file. Please check
+                  permissions in the modules directory', 'error');
+                return;
+              }
+              $archive_errors = update_manager_archive_verify($project, $local_cache, $directory);
+              if (!empty($archive_errors)) {
+                foreach ($archive_errors as $error) {
+                  drupal_set_message($error, 'error');
+                }
+              }
+              $project_location = $directory . '/' . $project;
+              try {
+                $updater = Updater::factory($project_location);
+              }
+              catch (Exception $e) {
+                drupal_set_message($e->getMessage(), 'error');
+                return;
+              }
+              $project_real_location = drupal_realpath($project_location);
+              $arguments = array(
+                'project' => $project,
+                'updater_name' => get_class($updater),
+                'local_url' => $project_real_location,
+              );
+              module_load_include('inc', 'update', 'update.authorize');
+              $filetransfer = new FileTransferLocal(DRUPAL_ROOT);
+              call_user_func_array('update_authorize_run_install', array_merge(array($filetransfer), $arguments));
+
+              drupal_set_message('It appears the module was downloaded and
+                extracted. You can now ' . l('enable this module'. 'admin/modules') . '.');
+            }
+          }
+        }
+        else {
+          drupal_set_message('Cannot download this module.  The Drpual project
+            name is not set. You may have to manually download this module, and
+            if possible encourage the developers to set the project name if it
+            has been fully published on Drupal.org already.', 'error');
+        }
         break;
       default:
         break;
@@ -220,7 +615,6 @@ function tripal_core_extensions_form_submit($form, &$form_state) {
   }
 }
 
-
 /**
  * The theme function for rendering each element's table.
  *
@@ -259,4 +653,99 @@ function theme_tripal_core_extensions_form_tables($variables) {
    );
 
   return theme_table($table);
+}
+
+/**
+ * A callback function for the form
+ *
+ * @return unknown
+ */
+function tripal_core_extensions_form_ajax_callback($form, $form_state) {
+  // This function need not do anything as the form will take care of
+  // updates needed. We just need to return the form.
+  return $form;
+}
+
+/**
+ * Determines the most recent downloadable package for a module.
+ *
+ * This function will connect to Drupal.org's RSS feed for a project and
+ * determine the most recent version of the module and return the URL
+ * for it's package.
+ *
+ * @param $project
+ */
+function tripal_core_extensions_get_latest_module_version($project_name) {
+  // First use the Drupal RESTful API to get the project
+  $url = "https://www.drupal.org/api-d7/node.json?type=project_module&field_project_machine_name=$project_name";
+  $result = json_decode(file_get_contents($url), TRUE);
+  $project = $result['list'][0];
+  $nid = $project['nid'];
+
+  // Second get the releases for this node and find releases for this Drupal
+  $url = "https://www.drupal.org/api-d7/node.json?type=project_release&field_release_project=$nid";
+  $result = json_decode(file_get_contents($url), TRUE);
+  $releases = $result['list'];
+  $drupal_version = VERSION;
+  $drupal_major = preg_replace('/^(\d+)\.\d+$/', "$1", $drupal_version);
+  $best_release = NULL;
+  foreach ($releases as $release) {
+    // This release must match the Drupal major version or it won't
+    // be compatiable. If it doesn't we'll skip it.
+    $release_version = $release['field_release_version'];
+    $release_drupal = preg_replace('/^(\d+)\.x-.*$/', "$1", $release_version);
+    if ($release_drupal != $drupal_major) {
+      continue;
+    }
+
+    // Set the current release to be the best one. On successive iterations
+    // we'll check to see if that is still true.
+    if ($best_release == NULL) {
+      $best_release = $release;
+      continue;
+    }
+    if ($release['field_release_version_major'] > $best_release['field_release_version_major']) {
+      $best_release = $release;
+      continue;
+    }
+    if ($release['field_release_version_patch'] > $best_release['field_release_version_patch']) {
+      $best_release = $release;
+      continue;
+    }
+
+    // If the best version has no extra part then let's keep it as this is the
+    // most stable release.
+    if (!$best_release['field_release_version_extra']) {
+      continue;
+    }
+
+    // Convert the 'extra' part to a numeric value that will let us compare
+    // the ranks of the release versions.
+    $extra_rank = array(
+      'unstable' => 1,
+      'alpha' => 2,
+      'beta' => 3,
+      'rc' => 4,
+    );
+    $matches = array();
+    $this_extra = 0;
+    if (preg_match('/^(.*?)(\d+)$/', $release['field_release_version_extra'], $matches)) {
+      $this_extra = $extra_rank[$matches[1]] . "." . $matches[2];
+    }
+    $best_extra = 0;
+    if (preg_match('/^(.*?)(\d+)$/', $best_release['field_release_version_extra'], $matches)) {
+      $best_extra = $extra_rank[$matches[1]] . "." . $matches[2];
+    }
+    if ($this_extra > $best_extra) {
+      $best_release = $release;
+      continue;
+    }
+  }
+  // If we have a best result then build the download string.
+  // TODO: we may need to make another web services call to get the actual
+  // download path, but for now we'll hard code the construction of it.
+  if ($best_release) {
+    return 'http://ftp.drupal.org/files/projects/' . $project_name . '-' . $best_release['field_release_version'] . '.tar.gz';
+  }
+  return '';
 }

+ 11 - 9
tripal_core/theme/css/tripal.css

@@ -202,14 +202,6 @@ div.messages.tripal-site-admin-only{
   padding: 0px;
 }
 
-.tripal-serverity-string {
-  font-weight: bold;
-}
-
-.tripal-serverity-string.critical, .tripal-serverity-string.error {
-  color: #FF0000;
-}
-
 .tripal-code {
   font-family: "Courier New", Courier, monospace;
   word-wrap: break-word;
@@ -218,7 +210,17 @@ div.messages.tripal-site-admin-only{
   padding-bottom: 10px;
 }
 
-.tripal-serverity-string.warning {
+.tripal-severity-string {
+  font-weight: bold;
+}
+
+.tripal-severity-string-critical, .tripal-severity-string-error, 
+.tripal-severity-string.critical, .tripal-severity-string.error {
+  color: #FF0000;
+}
+
+.tripal-severity-string-warning,
+.tripal-severity-string.warning {
   color: #FF8000;
 }
 

+ 1 - 1
tripal_core/tripal_core.module

@@ -163,7 +163,7 @@ function tripal_core_menu() {
     'weight' => -4
   );
   $items['admin/tripal/extension'] = array(
-    'title' => 'Extensions',
+    'title' => 'Available Extensions',
     'description' => t('Configuration for Tripal extensions.'),
     'access arguments' => array('administer tripal'),
     'type' => MENU_NORMAL_ITEM,