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/"; $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(); $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_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. 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 .= "Tripal $tripal_version and Chado $chado_version."; } elseif ($tripal_version == 'any' and $chado_version != 'any') { $summary_message .= "any Tripal version and Chado $chado_version."; } elseif ($tripal_version != 'any' and $chado_version == 'any') { $summary_message .= "Tripal $tripal_version and any Chado version."; } elseif ($tripal_version == 'any' and $chado_version == 'any') { $summary_message .= "any Tripal version and any Chado version."; } $form['filter_summary'] = array( '#type' => 'item', '#markup' => $summary_message, ); // 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' => '
" . $extension['description'] . "
", ); // Add an import button to each of types that can support import. switch ($type) { case 'Bulk Loader Template': if (!$incompatible and !$is_installed and !$project) { $form[$type][$guid]['import'] = array( '#type' => 'submit', '#value' => "Import Loader", '#name' => "import-" . $guid, ); } break; case 'Materialized View': if (!$incompatible and !$is_installed and !$project) { $form[$type][$guid]['import'] = array( '#type' => 'submit', '#value' => "Import MView", '#name' => "import-" . $guid, ); } break; case 'Extension Module': 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'; $extension_index++; } // Now create and theme the pager. $pager = array( 'tags' => array(), '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' => $pager, ); $type_index++; } } /** * Process the import buttons. * * @param $form * @param $form_state */ function tripal_core_extensions_form_submit($form, &$form_state) { // get the guid $clicked_button = $form_state['clicked_button']['#name']; $guid = preg_replace('/^import-(\d+)$/', "$1", $clicked_button); if ($guid) { $namespace = "http://tripal.info/rss/extensions/"; $extension = $form_state['values']['extension-' . $guid]; $type = $extension['category']; switch ($type) { case 'Bulk Loader Template': $options = array( 'template_name' => $extension['title'], 'template_array' => $extension[$namespace]['bulkldr_export'], 'strict' => TRUE, ); $errors = array(); $warnings = array(); $success = tripal_insert_bulk_loader_template($options, $errors, $warnings); if ($success) { drupal_set_message("Bulk loader succesfully added."); } else { drupal_set_message("Error importing this bulk loader.", 'error'); if (count($errors) > 0) { foreach($errors as $field => $message) { drupal_set_message($message, 'error'); } } } break; case 'Materialized View': $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 // we should make sure the array is valid and there is no hidden // PHP code. $schema_array = array(); $success = eval("\$schema_array = $mview_schema;"); $error = chado_validate_custom_table_schema($schema_array); if ($error) { drupal_set_message("Cannot import Materialized View: $error", "error"); } 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; } } } /** * The theme function for rendering each element's table. * * @param $variables */ function theme_tripal_core_extensions_form_tables($variables) { $element = $variables['element']; $headers = array( array( 'data' => drupal_render($element['header']), 'colspan' => 2, ) ); $button = array_key_exists('import', $element) ? drupal_render($element['import']) : ' '; $rows = array( array( array( 'data' => drupal_render($element['details']), ), array( 'data' => $button, 'width' => '5%', 'align' => 'right', ), ), ); $table = array( 'header' => $headers, 'rows' => $rows, 'attributes' => array(), 'sticky' => FALSE, 'caption' => '', 'colgroups' => array(), 'empty' => '', ); 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 ''; }