From 867ad2b2ba76c260b2b0ebfcfa7593edf3a2edbb Mon Sep 17 00:00:00 2001 From: Leighton Whiting Date: Wed, 28 Nov 2012 12:44:04 -0700 Subject: [PATCH] Changes suggested by YesCT Inclusion of old update method --- .../config/project_browser.settings.yml | 7 + .../project_browser/css/project_browser.admin.css | 174 ++++ core/modules/project_browser/images/arrow-asc.png | 5 + core/modules/project_browser/images/arrow-desc.png | 4 + core/modules/project_browser/images/circle.png | 3 + core/modules/project_browser/images/red-x.png | 7 + .../js/project_browser_more_link.js | 41 + .../js/project_browser_select_releases.js | 22 + .../project_browser/Tests/ProjectBrowserTest.php | 115 +++ .../project_browser/project_browser.admin.inc | 44 + core/modules/project_browser/project_browser.inc | 1061 ++++++++++++++++++++ core/modules/project_browser/project_browser.info | 7 + .../modules/project_browser/project_browser.module | 536 ++++++++++ .../project_browser/project_browser.pages.inc | 565 +++++++++++ .../tests/project_browser_test.info | 6 + .../tests/project_browser_test.module | 343 +++++++ .../theme/project-browser-block.tpl.php | 21 + .../theme/project-browser-install-queue.tpl.php | 15 + .../theme/project-browser-install.tpl.php | 21 + .../theme/project-browser-list.tpl.php | 24 + .../theme/project-browser-project.tpl.php | 58 ++ 21 files changed, 3079 insertions(+), 0 deletions(-) create mode 100644 core/modules/project_browser/config/project_browser.settings.yml create mode 100644 core/modules/project_browser/css/project_browser.admin.css create mode 100644 core/modules/project_browser/images/arrow-asc.png create mode 100644 core/modules/project_browser/images/arrow-desc.png create mode 100644 core/modules/project_browser/images/circle.png create mode 100644 core/modules/project_browser/images/red-x.png create mode 100644 core/modules/project_browser/js/project_browser_more_link.js create mode 100644 core/modules/project_browser/js/project_browser_select_releases.js create mode 100644 core/modules/project_browser/lib/Drupal/project_browser/Tests/ProjectBrowserTest.php create mode 100644 core/modules/project_browser/project_browser.admin.inc create mode 100644 core/modules/project_browser/project_browser.inc create mode 100644 core/modules/project_browser/project_browser.info create mode 100644 core/modules/project_browser/project_browser.module create mode 100644 core/modules/project_browser/project_browser.pages.inc create mode 100644 core/modules/project_browser/tests/project_browser_test.info create mode 100644 core/modules/project_browser/tests/project_browser_test.module create mode 100644 core/modules/project_browser/theme/project-browser-block.tpl.php create mode 100644 core/modules/project_browser/theme/project-browser-install-queue.tpl.php create mode 100644 core/modules/project_browser/theme/project-browser-install.tpl.php create mode 100644 core/modules/project_browser/theme/project-browser-list.tpl.php create mode 100644 core/modules/project_browser/theme/project-browser-project.tpl.php diff --git a/core/modules/project_browser/config/project_browser.settings.yml b/core/modules/project_browser/config/project_browser.settings.yml new file mode 100644 index 0000000..fd66849 --- /dev/null +++ b/core/modules/project_browser/config/project_browser.settings.yml @@ -0,0 +1,7 @@ +cache_lifetime: 86400 +# @todo - Change the link once drupal.org is ready. +default_server: + http://drupal:drupal@pbs-drupal_7.redesign.devdrupal.org/project_browser/server: + name: Drupal.org + method: json +project_browser_servers: "" diff --git a/core/modules/project_browser/css/project_browser.admin.css b/core/modules/project_browser/css/project_browser.admin.css new file mode 100644 index 0000000..0b4e08f --- /dev/null +++ b/core/modules/project_browser/css/project_browser.admin.css @@ -0,0 +1,174 @@ +/* -------------- Project Browser ------------- */ + +#project-browser-install-button-form { + clear: both; + margin: 10px 0 0; +} +#project-browser-install-queue { + margin: 5px 0 0; +} +.project-browser-install-queue-item { + margin: 5px 0; +} +.project-browser-install-queue-items { + clear: both; + margin: 0 0 15px; +} +.project-browser-install-link { + clear: both; +} +.project-browser-install-queue-item a { + padding-left: 20px; /* LTR */ + background: url("../images/red-x.png") no-repeat scroll left 2px transparent; +} +.project-browser-selected-release { + display: none; +} +.project-browser-show-releases-link { + cursor: pointer; +} + +/* ------------------ List Styles ------------------ */ + +div.item-list ul.project-browser-sort-list, +div.item-list ul.project-browser-servers-list { + margin: 0; +} +div.item-list ul.project-browser-sort-list li, +div.item-list ul.project-browser-servers-list li { + display: inline; + list-style-image: none; + margin-right: 10px; /* LTR */ +} +div.item-list ul.project-browser-sort-list li a, +div.item-list ul.project-browser-servers-list li a { + color: #0074bd; +} +div.item-list ul.project-browser-sort-list li.sort-active a, +div.item-list ul.project-browser-servers-list li.server-active a { + color: #000000; +} +div.item-list ul.project-browser-sort-list li.sort-header, +div.item-list ul.project-browser-servers-list li.server-header { + font-weight: bold; +} +div.item-list ul.project-browser-sort-list li.sort-asc { + background: url("../images/arrow-asc.png") no-repeat scroll right 2px transparent; + padding-right: 15px; /* LTR */ +} +div.item-list ul.project-browser-sort-list li.sort-desc { + background: url("../images/arrow-desc.png") no-repeat scroll right 2px transparent; + padding-right: 15px; /* LTR */ +} +a.show-more { + float: right; /* LTR */ +} + +/* ------------------ Project ----------------- */ + +.project-extra { + clear: both; + color: gray; + text-align: right; /* LTR */ +} +.project-author { + color: gray; + font-size: 0.9em; +} +.project-updated { + color: gray; + font-size: 0.9em; +} +.project-image { + float: left; /* LTR */ + margin-right: 10px; /* LTR */ +} +.project-image img { + max-height: 150px; + max-width: 200px; +} +#project-browser-main ul, #project-browser-main ol { + list-style-position: inside; +} +.project-item { + border-top: 1px solid #e0e0d8; + clear: both; + padding: 10px; + position: relative; +} +.project-item-first { + clear: both; + padding: 10px; + position: relative; +} +div.project-status { + position: absolute; + right: 10px; /* LTR */ + text-align: right; /* LTR */ + top: 5px; +} +div.project-information { + +} +.project-browser-install-main { + padding: 10px; + float: right; /* LTR */ + width: 76%; +} +.install-disabled { + color: gray; +} +.install-enabled { + color: green; +} +.project-browser-install-sidebar-left { + float: left; /* LTR */ + width: 19%; + padding: 10px; +} +div.project-title { + font-size: 18px; +} +fieldset#edit-category, +fieldset#edit-version { + border: none; +} +div.install-item-prefix { + float:left; /* LTR */ + margin-left: 50px; /* LTR */ +} + +/* -------------- Project Browser ------------- */ + +#project-browser-main div.form-item-install { + float: right; /* LTR */ +} +#project-browser div.project-browser-region { + min-height: 1px; +} +#project-browser div#project-browser-main { + width: 75%; + float: left; /* LTR */ + margin-right: 1%; /* LTR */ +} +#project-browser div#project-browser-sidebar-right { + width: 23%; + float: right; /* LTR */ +} +#project-browser div.project_browser_block { + margin-bottom: 20px; + border: 1px solid #cccccc; +} +#project-browser .project-browser-region .project_browser_block { + clear: both; +} +#project-browser div.project_browser_block h2 { + float: none; + font-size: 1em; + margin: 0; + padding: 3px 10px; + background: none repeat scroll 0 0 #e0e0d8; +} +#project-browser div#project-browser-sidebar-right div.project_browser_block div.content { + padding: 5px 10px; +} diff --git a/core/modules/project_browser/images/arrow-asc.png b/core/modules/project_browser/images/arrow-asc.png new file mode 100644 index 0000000..a3ccabc --- /dev/null +++ b/core/modules/project_browser/images/arrow-asc.png @@ -0,0 +1,5 @@ +PNG + + IHDR H%v?PLTEkR%tRNS@fIDATc`@L L LL + +Li|q)IENDB` \ No newline at end of file diff --git a/core/modules/project_browser/images/arrow-desc.png b/core/modules/project_browser/images/arrow-desc.png new file mode 100644 index 0000000..2edbb17 --- /dev/null +++ b/core/modules/project_browser/images/arrow-desc.png @@ -0,0 +1,4 @@ +PNG + + IHDR H%v?PLTEkR%tRNS@fIDATxc`@0`o``g`` +KzHIENDB` \ No newline at end of file diff --git a/core/modules/project_browser/images/circle.png b/core/modules/project_browser/images/circle.png new file mode 100644 index 0000000..6b2d63f --- /dev/null +++ b/core/modules/project_browser/images/circle.png @@ -0,0 +1,3 @@ +PNG + + IHDR6|JPLTEE<tRNS@fIDAT[c`cfe ( $b IENDB` \ No newline at end of file diff --git a/core/modules/project_browser/images/red-x.png b/core/modules/project_browser/images/red-x.png new file mode 100644 index 0000000..486390c --- /dev/null +++ b/core/modules/project_browser/images/red-x.png @@ -0,0 +1,7 @@ +PNG + + IHDRaIDATxڥ?HBQƣr #p(2hAk +!GP2ISiъ +:W_O"sw^gH^3כT(Ppc"31!h>_af Epd!H ߯! LNj Mp7<ǃpl2LIlo#6; a!vp˅r4 +u +RpNE>yHnlG.RWR B?%a<a2#=4}DrOu_ TWXTPjmA(`%xZZjKjFUtNjX'';GWXCW83p~zo$B{;rtAjX) |!uwsֆhx-=2ةQ-riIMQ(r?6{L~_ͧGIENDB` \ No newline at end of file diff --git a/core/modules/project_browser/js/project_browser_more_link.js b/core/modules/project_browser/js/project_browser_more_link.js new file mode 100644 index 0000000..c389cf1 --- /dev/null +++ b/core/modules/project_browser/js/project_browser_more_link.js @@ -0,0 +1,41 @@ +/** + * @file + * Makes the 'More' button toggle the display of the full project texts. + * + * The project descriptions are by default trimmed to a certain height. When + * the user clicks the more link, then the full text is shown. + */ +(function ($) { + + "use strict"; + + Drupal.behaviors.projectBrowserMoreLink = { + attach: function (context, settings) { + // The height of the content block when it's not expanded + var adjustheight = 80; + // The "more" link text + var moreText = Drupal.t('More'); + // The "less" link text + var lessText = Drupal.t('Less'); + + $(".project-information .project-description").each(function(index) { + if ($(this).height() > adjustheight) + { + $(this).css('height', adjustheight).css('overflow', 'hidden'); + $(this).parents(".project-information").append(''); + } + }); + + $("a.show-more").text(moreText); + + $(".show-more").toggle(function() { + $(this).parents("div:first").find(".project-description").css('height', 'auto').css('overflow', 'visible'); + $(this).text(lessText); + }, function() { + $(this).parents("div:first").find(".project-description").css('height', adjustheight).css('overflow', 'hidden'); + $(this).text(moreText); + }); + } + }; + +})(jQuery); diff --git a/core/modules/project_browser/js/project_browser_select_releases.js b/core/modules/project_browser/js/project_browser_select_releases.js new file mode 100644 index 0000000..05a4614 --- /dev/null +++ b/core/modules/project_browser/js/project_browser_select_releases.js @@ -0,0 +1,22 @@ +/** + * @file + * Shows the default Select Releases page until 'Show All Releases' is clicked. + */ +(function ($) { + + "use strict"; + + Drupal.behaviors.projectBrowserSelectReleases = { + attach: function (context, settings) { + $('.project-browser-releases-wrapper').hide(); + $('.project-browser-selected-release').show(); + + $('.project-browser-show-releases-link').click(function() { + var target = $(this).attr('rel'); + $('.project-browser-release-' + target).show(); + $('.project-browser-selected-release-' + target).hide(); + }) + } + }; + +})(jQuery); diff --git a/core/modules/project_browser/lib/Drupal/project_browser/Tests/ProjectBrowserTest.php b/core/modules/project_browser/lib/Drupal/project_browser/Tests/ProjectBrowserTest.php new file mode 100644 index 0000000..6aff12b --- /dev/null +++ b/core/modules/project_browser/lib/Drupal/project_browser/Tests/ProjectBrowserTest.php @@ -0,0 +1,115 @@ + 'Project Browser Install Project Test', + 'description' => 'Attempts to install a project.', + 'group' => 'Project Browser', + ); + } + + /** + * Overrides \Drupal\simpletest\WebTestBase::setUp(). + */ + function setUp() { + parent::setUp(); + + // Set the default server variable. + $server_url = url('project_browser_test/query', array('absolute' => TRUE)); + $config = config('project_browser.settings'); + $config->set('default_server', array( + $server_url => array( + 'name' => 'Test Server', + 'method' => 'json', + ), + )); + $config->save(); + + // Create and log in our privileged user. + $this->privilegedUser = $this->drupalCreateUser(array( + 'use project browser', + )); + $this->drupalLogin($this->privilegedUser); + } + + /** + * Tests a search for the Token string, ensuring that token shows. + */ + public function testProjectBrowserSearchToken() { + // Search for 'token'. + $edit = array(); + $edit['search_text'] = 'token'; + $this->drupalPost('admin/modules/project-browser/modules', $edit, t('Filter')); + $this->assertResponse(200); + $this->assertText('Showing 1 to'); + $this->assertText('Tokens are small bits of text'); + } + + /** + * Tests that the Projects Listing displays properly with the default filters. + */ + public function testProjectBrowserGetProjects() { + // Attempt to fetch the default projects. + $edit = array(); + $edit['search_text'] = ''; + $this->drupalPost('admin/modules/project-browser/modules', $edit, t('Filter')); + $this->assertResponse(200); + $this->assertText('Showing 1 to'); + } + + /** + * Tests to make sure that the enabled project detection works. + */ + public function testProjectBrowserProjectEnabled() { + // Make sure project enabled detection works. + module_load_include('inc', 'project_browser', 'project_browser'); + $this->assertTrue(_project_browser_is_project_enabled('module', 'project_browser'), + 'Make sure project enabled detection works.'); + } + + /** + * Tests that adding and removing items from the queue works properly. + */ + public function testProjectBrowserAddRemoveQueue() { + // Refresh the page. + $this->drupalGet('admin/modules/project-browser/modules'); + + // Simulate adding a project to the install queue. + $this->drupalGet('project-browser/nojs/install-queue/add/views', + array('query' => array('destination' => 'admin/modules/project-browser'))); + $this->assertNoText('Install queue is empty.'); + $this->assertNoText('Error: The project was not found.'); + + // Simulate removing a project from the install queue. + $this->drupalGet('project-browser/nojs/install-queue/remove/views', + array('query' => array('destination' => 'admin/modules/project-browser'))); + $this->assertText('Install queue is empty.'); + $this->assertNoText('Error: The project was not found.'); + } +} diff --git a/core/modules/project_browser/project_browser.admin.inc b/core/modules/project_browser/project_browser.admin.inc new file mode 100644 index 0000000..8c42b55 --- /dev/null +++ b/core/modules/project_browser/project_browser.admin.inc @@ -0,0 +1,44 @@ + 'fieldset', + '#title' => t('Main settings'), + '#collapsible' => FALSE, + '#collapsed' => FALSE, + ); + // Because this is a pluggable system, there can be other repositories besides + // Drupal.org. + $form['main']['project_browser_servers'] = array( + '#type' => 'textarea', + '#title' => t('Repositories'), + '#default_value' => config('project_browser.settings')->get('project_browser_servers'), + '#description' => t("Add new repositories to use for the Project Browser, one per line, in + the 'url|method|Site Name' format. Drupal.org is added by default, and doesn't need to be + set here."), + '#required' => FALSE, + ); + + return $form; +} + +/** + * Form submission handler for project_browser_admin_form(). + */ +function project_browser_admin_form_submit($form, &$form_state) { + $config = config('project_browser.settings'); + $config->set('project_browser_servers', $form_state['values']['project_browser_servers']); + $config->save(); +} diff --git a/core/modules/project_browser/project_browser.inc b/core/modules/project_browser/project_browser.inc new file mode 100644 index 0000000..c4cf222 --- /dev/null +++ b/core/modules/project_browser/project_browser.inc @@ -0,0 +1,1061 @@ + $queued_projects)); +} + +/** + * Form constructor for the install button for the Install Queue block. + * + * Since the selected projects are stored in the $_SESSION variable, + * no real processing is done, we just redirect to the install/select_versions + * page. + * + * @see project_browser_preprocess_project_browser_install_queue() + * @ingroup forms + */ +function project_browser_install_button_form($form, &$form_state) { + $form['#attributes']['id'] = 'project-browser-install-button-form'; + $form['submit'] = array( + '#type' => 'submit', + '#value' => 'Install', + ); + + $form['#action'] = url('admin/modules/project-browser/install/select_versions'); + + return $form; +} + +/** + * Form constructor for the filters form. + * + * This includes categories and the string search box, and the $type is stored. + * + * @param string $type + * The type of project (module or theme). + * + * @see project_browser_filters_form_submit() + * @ingroup forms + */ +function project_browser_filters_form($form, &$form_state, $type) { + $form['search_text'] = array( + '#type' => 'textfield', + '#size' => '25', + '#title' => t('Search String'), + '#default_value' => isset($_SESSION['project_browser_text_filter_' . $type]) ? $_SESSION['project_browser_text_filter_' . $type] : '', + ); + + // Add the category filter if there are categories. + if ($categories = project_browser_get_categories($type)) { + array_unshift($categories, t('-- Any --')); + $form['category'] = array( + '#type' => 'select', + '#title' => t('Category'), + '#options' => $categories, + '#default_value' => isset($_SESSION['project_browser_category_filter_' . $type]) ? $_SESSION['project_browser_category_filter_' . $type] : NULL, + ); + } + + $form['project_type'] = array( + '#type' => 'value', + '#value' => $type, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Filter'), + ); + + return $form; +} + +/** + * Form submission handler for project_browser_filters_form(). + * + * All that we do here is store the selected category and search string + * in the $_SESSION variable. + */ +function project_browser_filters_form_submit($form, &$form_state) { + $type = $form_state['values']['project_type']; + if (!empty($form_state['values']['category'])) { + $_SESSION['project_browser_category_filter_' . $type] = array($form_state['values']['category'] => $form_state['values']['category']); + } + else { + $_SESSION['project_browser_category_filter_' . $type] = array(); + } + $_SESSION['project_browser_text_filter_' . $type] = $form_state['values']['search_text']; +} + +/** + * Builds a themed sort widget for the results. + * + * These are links which can be clicked/toggled to select and change direction. + * + * @param array $sort_options + * An array of sort options. + * @param string $current_sort_option + * The currently selected sort option. + * @param string $current_sort_direction + * The currently selected sort direction. + * + * @return string + * A themed list of sort options. + */ +function project_browser_get_sort_widget(array $sort_options, $current_sort_option, $current_sort_direction) { + $sort_list = array(); + $sort_list[] = array( + '#markup' => t('Sort by:'), + '#wrapper_attributes' => array( + 'class' => array('sort-header'), + ), + ); + + foreach ($sort_options as $sort_option) { + $classes = array(); + $query = array( + 'order_by' => $sort_option['method'], + 'sort' => $sort_option['default_sort'], + ); + + // If the sort option is currently active, handle it differently. + if ($current_sort_option == $sort_option['method']) { + $classes[] = 'sort-active'; + $classes[] = 'sort-' . $current_sort_direction; + + // Set the direction of the sort link to the opposite of what it + // currently is. + if ($current_sort_direction == $query['sort']) { + if ($query['sort'] == 'desc') { + $query['sort'] = 'asc'; + } + else { + $query['sort'] = 'desc'; + } + } + } + else { + $classes[] = 'sort-inactive'; + } + $sort_list[] = array( + '#markup' => l($sort_option['name'], current_path(), array('query' => $query, 'class' => array())), + '#wrapper_attributes' => array( + 'class' => $classes, + ), + ); + } + + return theme('item_list', array( + 'items' => $sort_list, + 'type' => 'ul', + 'attributes' => array('class' => array('project-browser-sort-list')) + ) + ); +} + +/** + * Builds a themed widget to select the server. + * + * This is only called if there are more than one server enabled in the + * settings. + * + * @param array $servers + * An array of servers that should be available as options. + * @param string $current_server + * The currently selected server. + * + * @return string + * A themed server select widget. + */ +function project_browser_get_server_widget(array $servers, $current_server) { + $list = array(); + $list[] = array( + 'data' => t('Repository:'), + 'class' => array('server-header') + ); + $current_path = drupal_get_path_alias($_GET['q']); + + $i = 0; + + foreach ($servers as $url => $server) { + $classes = array(); + $query = array( + 'repository' => $i, + ); + + // If the sort option is currently active, handle it differently. + if ($current_server == $i) { + $classes[] = 'server-active'; + } + else { + $classes[] = 'server-inactive'; + } + + $list[] = array( + '#markup' => l($server['name'], $current_path, array('query' => $query, 'class' => array())), + '#wrapper_attributes' => array( + 'class' => $classes, + ), + ); + + $i += 1; + } + + return theme('item_list', array( + 'items' => $list, + 'type' => 'ul', + 'attributes' => array('class' => array('project-browser-servers-list')) + ) + ); +} + +/** + * Builds and returns an array of sort options, keyed by method. + * + * @param bool $full + * (optional) Set this to TRUE if you want to get all of the supported sort + * methods. Defaults to FALSE. + * + * @return array + * An array of sort options, keyed by method. + */ +function project_browser_get_sort_options($full = FALSE) { + $sort_options = array( + 'score' => array('method' => 'score', 'name' => t('Relevancy'), 'default_sort' => 'desc'), + 'usage' => array('method' => 'usage', 'name' => t('Most installed'), 'default_sort' => 'desc'), + 'title' => array('method' => 'title', 'name' => t('Title'), 'default_sort' => 'asc'), + 'name' => array('method' => 'name', 'name' => t('Author'), 'default_sort' => 'asc'), + 'latest_release' => array('method' => 'latest_release', 'name' => t('Latest release'), 'default_sort' => 'desc'), + ); + + if ($full) { + $sort_options['type'] = array( + 'method' => 'type', + 'name' => t('Type'), + 'default_sort' => 'asc' + ); + $sort_options['created'] = array( + 'method' => 'created', + 'name' => t('Date created'), + 'default_sort' => 'asc' + ); + $sort_options['latest_activity'] = array( + 'method' => 'latest_activity', + 'name' => t('Latest build'), + 'default_sort' => 'desc' + ); + } + + return $sort_options; +} + +/** + * Builds an array of all available categories for a project type. + * + * @param string $type + * The type of project to get the categories for. Example: 'module' or + * 'theme'. + * + * @return array|false + * Array containing all available categories or FALSE if no categories. + */ +function project_browser_get_categories($type) { + $categories = array(); + + // Get which server to use from $_SESSION. + if (isset($_SESSION['project_browser_server_filter'])) { + $use_server = $_SESSION['project_browser_server_filter']; + } + else { + $use_server = 0; + } + + $categories_raw = project_browser_fetch_categories($type, $use_server); + + if (is_array($categories_raw) AND !empty($categories_raw)) { + foreach ($categories_raw as $url => $cats) { + foreach ($cats as $key => $value) { + // Create a new key so that there are no duplicate categories from + // different sites. + $new_key = preg_replace('/[^a-z0-9_]+/', '_', strtolower($value)); + $categories[$new_key] = $value; + } + } + } + + if (is_array($categories) AND !empty($categories)) { + ksort($categories); + + return $categories; + } + return FALSE; +} + +/** + * Prepares the categories for sending to the servers as filters. + * + * @param array $raw_cats + * An array of categories from $form_state['values']. + * @param string $type + * The type of project to prepare the categories for, eg 'module' or 'theme'. + * + * @return array + * An array of server categories, keyed by server url. + */ +function project_browser_prepare_categories(array $raw_cats, $type) { + $categories = project_browser_fetch_categories($type); + + // Set the value of the categories to true if it is selected. + foreach ($categories as $url => $cats) { + foreach ($cats as $key => $value) { + $new_key = preg_replace('/[^a-z0-9_]+/', '_', strtolower($value)); + if (isset($raw_cats[$new_key]) AND $raw_cats[$new_key]) { + $categories[$url][$key] = TRUE; + } + else { + unset($categories[$url][$key]); + } + } + + // Unset the parent if there are no children. + if (empty($categories[$url])) { + unset($categories[$url]); + } + } + + return $categories; +} + +/** + * Checks if a project is enabled. + * + * @param string $type + * The type of project. Could be 'theme' or 'module'. + * @param string $name + * The short name of the project. + * + * @return bool + * TRUE if the project is enabled, FALSE otherwise. + */ +function _project_browser_is_project_enabled($type, $name) { + switch ($type) { + case 'module': + return module_exists($name); + break; + + case 'theme': + $themes = list_themes(); + return isset($themes[$name]); + break; + } + return FALSE; +} + +/** + * Gets the currently listed projects from the session. + * + * @return array + * An array of listed projects from the $_SESSION variable. + */ +function project_browser_get_listed_projects() { + if (isset($_SESSION['project_browser_listed_projects'])) { + return $_SESSION['project_browser_listed_projects']; + } + + return array(); +} + +/** + * Gets the currently queued projects from the $_SESSION variable. + * + * @param string|null $type + * (optional) The type of project (module or theme). Defaults to NULL, which + * will return projects of all types. + * + * @return array + * An array of projects that are queued for install. + */ +function project_browser_get_queued_projects($type = NULL) { + $projects = array(); + + if (isset($_SESSION['project_browser_install_list'])) { + foreach ($_SESSION['project_browser_install_list'] as $project) { + if (is_array($project) AND !empty($project)) { + if (isset($type) AND $type != $project['type']) { + continue; + } + else { + $projects[$project['name']] = $project; + } + } + } + } + + return $projects; +} + +/** + * Gets a release from a project and a release_name. + * + * @param string $release_name + * The name of the release, such as '7.x-1.2'. + * @param array $project + * The $project data array. + * + * @return array|false + * The release data array or FALSE if the release doesn't exist. + */ +function project_browser_get_release($release_name, array $project) { + $release_data = project_browser_get_project_release_data($project); + + return isset($release_data['releases'][$release_name]) ? $release_data['releases'][$release_name] : FALSE; +} + +/** + * Gets the newly installed projects from the session. + * + * @return array + * An array of all of the newly installed projects. + */ +function project_browser_get_installed_projects() { + $projects = array(); + + if (isset($_SESSION['project_browser_installed_projects'])) { + foreach ($_SESSION['project_browser_installed_projects'] as $project) { + if (is_array($project) AND !empty($project)) { + $projects[$project['name']] = $project; + } + } + } + + return $projects; +} + +/** + * Adds a project to the install queue $_SESSION variable. + * + * @param array $project + * An array of $project data for a single project. + */ +function project_browser_install_queue_add(array $project) { + $_SESSION['project_browser_install_list'][$project['name']] = $project; +} + +/** + * Removes a project from the install queue $_SESSION variable. + * + * @param string $project_name + * The name of the project to remove, such as 'views'. + */ +function project_browser_install_queue_remove($project_name) { + if (isset($_SESSION['project_browser_install_list'][$project_name])) { + unset($_SESSION['project_browser_install_list'][$project_name]); + } +} + +/** + * Gets the currently queued releases from the $_SESSION variable. + * + * @return array + * An array of the currently selected releases. + */ +function project_browser_get_queued_releases() { + $releases = array(); + + if (isset($_SESSION['project_browser_install_releases_list'])) { + foreach ($_SESSION['project_browser_install_releases_list'] as $short_name => $info) { + if (is_array($info['project']) AND !empty($info['project'])) { + $releases[$short_name] = $info; + } + } + } + + return $releases; +} + +/** + * Fetches results from the servers based on the parameters passed in. + * + * @param array $filters + * An associative array of queries to use to filter results, containing: + * - version: The Major Version of Drupal that is running on the Client. + * Example: '7' or '8'. + * - text: The text that was entered as the search query, or '' if none. + * Example: 'Link field'. + * - categories: An array of categories that were selected, if any. + * - type: The type of project being searched for. Example: 'module' or + * 'theme'. + * - page: The zero-based page number. + * - requested: How many results are requested per page. + * + * @return array + * An array of results formatted like: + * - total: The total number of results found for the filters. + * - projects: An array of projects returned for this page request, + * containing: + * - KEY: A project array keyed by the machine name: + * - type: The type of project this is. Can be 'module' or 'theme'. + * - title: The title of the project. + * - name: The machine name of the project. + * - author: The author's name. + * - description: Long project description text. + * - image: Absolute url to the image, if any. + * - usage: How many sites are using module. + * - project url: Absolute url to the project page, if any. + * - project status url: The absolute url of the update checker, + * formatted like how Drupal.org Update Status does it. + * - last updated: UNIX Timestamp of when the project was last updated. + * - maintenance status: Maintenance status. + * - development status: Development status. + * - rating: A rating on a scale of 1 to 10 of the project, if available + * - dependencies: An array of the dependencies of this module, by + * project shortname, if available. + */ +function project_browser_fetch_results(array $filters) { + $servers = project_browser_get_servers($filters['server']); + // Attempt to retrieve the cached version of this page. + $cid = md5(serialize(array_merge($filters, $servers))); + + if ($cache = cache()->get($cid)) { + return $cache->data; + } + + $results = array( + 'projects' => array(), + 'total' => 0, + ); + + unset($filters['server']); + + foreach ($servers as $url => $server) { + $local_filters = $filters; + + // We are not using this right now because we only expect to handle 1 + // server at a time currently. + // $local_filters['requested'] = floor($filters['requested'] / count($servers)); + + // Send only the relevant categories to the server. + if (isset($filters['categories'])) { + if (!isset($filters['categories'][$url])) { + // Don't call a server for results if categories are being used, and + // none of them belong to the server. + continue; + } + $local_filters['categories'] = $filters['categories'][$url]; + } + + // Use XMLRPC if it is set. + if ($server['method'] == 'xmlrpc') { + $results_raw = xmlrpc($url, array( + 'project_browser_server.fetch_results' => array($local_filters), + )); + + // Check for errors. + if ($error = xmlrpc_error() AND $error->is_error) { + drupal_set_message(t("Encountered an error when trying to fetch results from @name. Error @code : @message", + array('@name' => $server['name'], '@code' => $error->code, '@message' => $error->message))); + continue; + } + } + + // Use json if it is set. + if ($server['method'] == 'json') { + $local_filters['method'] = 'query'; + if (isset($local_filters['categories'])) { + $local_filters['categories'] = serialize($local_filters['categories']); + } + + $query_url = $url . '/query/' . $local_filters['type'] . '/8?' . http_build_query($local_filters, FALSE, '&'); + $response = drupal_http_request($query_url); + if ($response->code == '200') { + $results_raw = drupal_json_decode($response->data); + } + else { + drupal_set_message(t("Encountered an error when trying to fetch results from @name. Error @code : @message", + array('@name' => $server['name'], '@code' => $response->code, '@message' => $response->error))); + continue; + } + } + + if (isset($results_raw['total'])) { + $results['total'] += $results_raw['total']; + } + + if (!empty($results_raw['projects']) AND is_array($results_raw['projects'])) { + // Merge the results. + $results['projects'] = array_merge($results['projects'], $results_raw['projects']); + } + } + + cache()->set($cid, $results, REQUEST_TIME + config('project_browser.settings')->get('cache_lifetime')); + + return $results; +} + +/** + * Fetches categories from the servers based on the type of project. + * + * @param string $type + * The type of project we are getting categories for. Can be 'module' or + * 'theme' + * @param string $use_server + * (optional) The server to use. Defaults to 'all'. + * + * @return array + * Returns an array of the categories. + */ +function project_browser_fetch_categories($type, $use_server = 'all') { + $servers = project_browser_get_servers($use_server); + + // Attempt to retrieve the cached version of this page. + $cid = md5('categories_' . $type . serialize($servers)); + + if ($cache = cache()->get($cid)) { + return $cache->data; + } + else { + $categories = array(); + + foreach ($servers as $url => $server) { + $categories_raw = array(); + // Use xmlrpc if it is set. + if ($server['method'] == 'xmlrpc') { + $categories_raw = xmlrpc($url, array( + 'project_browser_server.fetch_categories' => array($type), + )); + + // Check for errors. + if ($error = xmlrpc_error() AND $error->is_error) { + drupal_set_message(t("Encountered an error when trying to fetch categories from @name. Error @code : @message", + array('@name' => $server['name'], '@code' => $error->code, '@message' => $error->message))); + continue; + } + } + + // Use json if it is set. + if ($server['method'] == 'json') { + $params = array( + 'method' => 'categories', + 'type' => $type, + ); + $response = drupal_http_request($url . '/categories/' . $type . '?' . http_build_query($params, FALSE, '&')); + if ($response->code == '200') { + $categories_raw = drupal_json_decode($response->data); + } + else { + drupal_set_message(t("Encountered an error when trying to fetch categories from @name. Error @code : @message", + array('@name' => $server['name'], '@code' => $response->code, '@message' => $response->error))); + continue; + } + } + + if (is_array($categories_raw) AND !empty($categories_raw)) { + $categories[$url] = $categories_raw; + } + } + + // Cache this for 24 hours. + cache()->set($cid, $categories, REQUEST_TIME + config('project_browser.settings')->get('cache_lifetime')); + } + + return $categories; +} + +/** + * Gets an array of servers to use for fetching results. + * + * @param string $use_server + * (optional) The server to use. Defaults to 'all'. + * + * @return array + * Returns an associative array of servers, populated from the + * project_browser_servers variable, in 'url => name' format. + */ +function project_browser_get_servers($use_server = 'all') { + $servers = config('project_browser.settings')->get('default_server'); + + if ($servers_raw = config('project_browser.settings')->get('project_browser_servers')) { + // Process the variable and add the servers to the list. + $custom_servers = array(); + + $list = explode("\n", $servers_raw); + $list = array_map('trim', $list); + $list = array_filter($list, 'strlen'); + + foreach ($list as $position => $text) { + $method = $name = $url = FALSE; + + $matches = array(); + if (preg_match('/(.*)\|(.*)\|(.*)/', $text, $matches)) { + $url = $matches[1]; + $method = $matches[2]; + $name = $matches[3]; + $custom_servers[$url] = array('name' => $name, 'method' => $method); + } + } + + $servers = array_merge($servers, $custom_servers); + } + + // Filter out servers if necessary. + if ($use_server !== 'all') { + $i = 0; + foreach ($servers as $url => $server) { + if ($use_server != $i) { + unset($servers[$url]); + } + $i += 1; + } + } + + return $servers; +} + +/** + * Uses the project status url to get the available releases for a project. + * + * @param array $project + * The project to get the releases for. + * + * @return array|false + * An array of releases for this project, or FALSE if it can't be found. + */ +function project_browser_get_project_release_data(array $project) { + $releases = array(); + $project['project_type'] = $project['type']; + $project['includes'] = array(); + + // Build the releases cache for this project. + module_load_include('inc', 'update', 'update.fetch'); + if (_update_process_fetch_task($project)) { + $data = _update_cache_get('available_releases::' . $project['name']); + if (isset($data->data) AND isset($data->data['releases']) AND is_array($data->data['releases'])) { + return $data->data; + } + } + + return FALSE; +} + +/** + * Downloads and installs a project using the update module. + * + * @todo - Use this new method once the patch in + * http://drupal.org/node/1846078 is committed. + * + * @param string $url + * The url of the release download. + * + * @return array + * An array indicating whether or not this was successful, and an error + * message if applicable. + */ +/** +function project_browser_download_project($url) { + module_load_include('inc', 'update', 'update.manager'); + module_load_include('inc', 'update', 'update.authorize'); + // Download the file. + $local_cache = update_manager_file_get($url); + if (!$local_cache) { + return array( + 'success' => FALSE, + 'message' => t('Unable to retrieve Drupal project from %url.', array('%url' => $url)), + ); + } + + try { + if ($arguments = update_manager_install_project($local_cache)) { + extract($arguments); + try { + $context = array(); + watchdog('debug', '
' . print_r($arguments, TRUE) . '
'); + update_authorize_batch_copy_project($project, $updater_name, $local_url, $filetransfer, $context); + return array( + 'success' => TRUE, + ); + } + catch (UpdaterException $e) { + return array( + 'success' => FALSE, + 'message' => $e->getMessage(), + ); + } + } + else { + return array( + 'success' => FALSE, + 'message' => t('The project could not be installed.'), + ); + } + } + catch (Exception $e) { + return array( + 'success' => FALSE, + 'message' => $e->getMessage(), + ); + } +} +*/ + +/** + * Downloads and installs a project using the update module. + * + * @param string $url + * The url of the release download. + * + * @return array + * An array indicating whether or not this was successful, and an error + * message if applicable. + */ +function project_browser_download_project($url) { + module_load_include('inc', 'update', 'update.manager'); + // Download the file. + $local_cache = update_manager_file_get($url); + if (!$local_cache) { + return array( + 'success' => FALSE, + 'message' => t('Unable to retrieve Drupal project from %url.', array('%url' => $url)), + ); + } + + // Try to extract it. + $directory = _update_manager_extract_directory(); + try { + $archive = update_manager_archive_extract($local_cache, $directory); + } + catch (Exception $e) { + return array( + 'success' => FALSE, + 'message' => $e->getMessage(), + ); + } + $files = $archive->listContents(); + if (!$files) { + return array( + 'success' => FALSE, + 'message' => t('Provided archive contains no files.'), + ); + } + + $project = strtok($files[0], '/\\'); + + $archive_errors = update_manager_archive_verify($project, $local_cache, $directory); + if (!empty($archive_errors)) { + if (!empty($archive_errors)) { + foreach ($archive_errors as $error) { + drupal_set_message(check_plain($error), 'error'); + } + } + return array( + 'success' => FALSE, + 'message' => array_shift($archive_errors), + ); + } + + // Make sure the Updater registry is loaded. + drupal_get_updaters(); + + $project_location = $directory . '/' . $project; + try { + $updater = Updater::factory($project_location); + } + catch (Exception $e) { + return array( + 'success' => FALSE, + 'message' => $e->getMessage(), + ); + } + + try { + $project_title = Updater::getProjectTitle($project_location); + } + catch (Exception $e) { + return array( + 'success' => FALSE, + 'message' => $e->getMessage(), + ); + } + + if ($updater->isInstalled()) { + return array( + 'success' => FALSE, + 'message' => t('%project is already installed.', array('%project' => $project_title)), + ); + } + + $project_real_location = drupal_realpath($project_location); + $updater_name = get_class($updater); + + if (fileowner($project_real_location) == fileowner(conf_path())) { + module_load_include('inc', 'update', 'update.authorize'); + $filetransfer = new Local(DRUPAL_ROOT); + + // Initialize some variables in the Batch API $context array. + $updater = new $updater_name($project_real_location); + + try { + if ($updater->isInstalled()) { + // This is an update. + $tasks = $updater->update($filetransfer); + } + else { + $tasks = $updater->install($filetransfer); + } + } + catch (UpdaterException $e) { + return array( + 'success' => FALSE, + 'message' => t('Error installing / updating. Error: @error', array('@error' => $e->getMessage())), + ); + } + } + else { + return array( + 'success' => FALSE, + 'message' => t('Permissions are not set up properly.'), + ); + } + + return array( + 'success' => TRUE, + ); +} + +/** + * Installs a single release of a project during batch, for example. + * + * @param string $release_name + * The name of the release, such as '7.x-1.2'. + * @param array $project + * The project data array. + * @param array &$context + * The context of the batch so that the results can be reported. + */ +function _project_browser_batch_install_release($release_name, array $project, array &$context) { + module_load_include('inc', 'project_browser', 'project_browser.pages'); + $release = project_browser_get_release($release_name, $project); + + $result = project_browser_download_project($release['download_link']); + + if ($result['success']) { + $context['results']['successes'][] = t('Successfully installed %project.', array('%project' => $project['title'])); + $context['message'] = t('Installed %project...', array('%project' => $project['title'])); + + // Add this to the session variable and remove it from the install_queue + // variable. + $_SESSION['project_browser_installed_projects'][$project['name']] = $project; + unset($_SESSION['project_browser_install_list'][$project['name']]); + } + else { + watchdog('project_browser', 'There was an error while installing %project. + !message', + array('%project' => $project['title'], '!message' => $result['message']), WATCHDOG_ERROR); + $context['results']['failures'][] = t('Error installing %project. Errors have been logged.', + array('%project' => $project['title'])); + $context['message'] = t('Error installing %project. !message', + array('%project' => $project['title'], '!message' => $result['message'])); + } +} + +/** + * Shows a message and finishes up the batch. + * + * If there were any errors, they are reported here with drupal_set_message(). + * The user is then redirected to the select versions page if there were errors, + * the install dependencies page if there were any detected missing + * dependencies, or the enable modules page if there were no errors. + * + * @param bool $success + * Whether or not the whole operation was successful. + * @param array $results + * An array of messages about any failures. + * @param array $operations + * An array of operations that need to be performed. + */ +function _project_browser_batch_install_releases_finished($success, array $results, array $operations) { + drupal_get_messages(); + + // Restore the maintenance mode to what it was at the start. + if (isset($_SESSION['maintenance_mode'])) { + config('system.maintenance')->set('enabled', $_SESSION['maintenance_mode'])->save(); + unset($_SESSION['maintenance_mode']); + } + + unset($_SESSION['project_browser_install_releases_list']); + if ($success) { + if (!empty($results)) { + if (!empty($results['failures'])) { + drupal_set_message(format_plural(count($results['failures']), 'Failed to install one project.', 'Failed to install @count projects.'), 'error'); + } + } + } + else { + drupal_set_message(t('Error installing projects.'), 'error'); + drupal_goto('admin/modules/project-browser/install/select_versions'); + } + + $projects = project_browser_get_installed_projects(); + $missing = project_browser_get_missing_dependencies($projects); + // If there are missing dependencies, go to install dependencies. + if (count($missing) > 0) { + drupal_goto('admin/modules/project-browser/install/install_dependencies'); + } + else { + drupal_goto('admin/modules/project-browser/install/enable'); + } +} + +/** + * Gets the dependencies for installed projects. + * + * @param array $projects + * An array of projects to get the missing dependencies for. + * + * @return array + * An array of missing dependencies, if any were detected. + */ +function project_browser_get_missing_dependencies(array $projects) { + $modules = system_rebuild_module_data(); + + $missing = array(); + + foreach ($projects as $project) { + if ($project['type'] == 'module') { + $dependency_check = TRUE; + $dependencies = array(); + if (isset($modules[$project['name']])) { + foreach ($modules[$project['name']]->info['dependencies'] as $dependency) { + if (!isset($modules[$dependency])) { + $dependencies[] = $dependency; + } + } + if (count($dependencies) > 0) { + $missing[$project['name']] = $dependencies; + } + } + else { + drupal_set_message(t('There was an error getting information for @module', + array('@module' => $project['name'])), 'error'); + } + } + } + + return $missing; +} diff --git a/core/modules/project_browser/project_browser.info b/core/modules/project_browser/project_browser.info new file mode 100644 index 0000000..343c840 --- /dev/null +++ b/core/modules/project_browser/project_browser.info @@ -0,0 +1,7 @@ +name = Project Browser +description = Allows users to browse for and install modules and themes. +dependencies[] = update +package = Core +version = VERSION +core = 8.x +configure = admin/config/development/project_browser diff --git a/core/modules/project_browser/project_browser.module b/core/modules/project_browser/project_browser.module new file mode 100644 index 0000000..1550158 --- /dev/null +++ b/core/modules/project_browser/project_browser.module @@ -0,0 +1,536 @@ +' . t("Provides a UI for users to browse for and install new modules and themes from within their Drupal admin interface.") . '

'; + break; + } + return $output; +} + +/** + * Implements hook_permission(). + */ +function project_browser_permission() { + return array( + 'use project browser' => array( + 'title' => t('Use Project Browser'), + 'description' => t('This allows the user to browse for and install new modules and themes using Project Browser.'), + 'restrict access' => TRUE, + ), + ); +} + +/** + * Implements hook_menu(). + */ +function project_browser_menu() { + $items = array(); + $items['admin/config/development/project_browser'] = array( + 'title' => 'Project Browser settings', + 'description' => 'Add new repositories and set other settings for Project Browser.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('project_browser_admin'), + 'access arguments' => array('access administration pages'), + 'file' => 'project_browser.admin.inc', + ); + + $items['admin/modules/project-browser'] = array( + 'title' => 'Project Browser', + 'description' => 'Browse and search for new modules.', + 'page callback' => 'project_browser_page', + 'page arguments' => array('module'), + 'access arguments' => array('use project browser'), + 'file' => 'project_browser.pages.inc', + ); + + $items['admin/modules/project-browser/modules'] = array( + 'title' => 'Modules', + 'description' => 'Browse and search for new modules.', + 'page callback' => 'project_browser_page', + 'page arguments' => array('module'), + 'access arguments' => array('use project browser'), + 'file' => 'project_browser.pages.inc', + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + + $items['admin/modules/project-browser/themes'] = array( + 'title' => 'Themes', + 'description' => 'Browse and search for new themes.', + 'page callback' => 'project_browser_page', + 'page arguments' => array('theme'), + 'access arguments' => array('use project browser'), + 'file' => 'project_browser.pages.inc', + 'type' => MENU_LOCAL_TASK, + ); + + $items['admin/modules/project-browser/install/%'] = array( + 'title' => 'Install', + 'page callback' => 'project_browser_installation_page', + 'page arguments' => array(4), + 'access arguments' => array('use project browser'), + 'type' => MENU_NORMAL_ITEM, + 'file' => 'project_browser.pages.inc', + ); + + $items['project-browser/%/install-queue/%/%'] = array( + 'page callback' => 'project_browser_install_queue_callback', + 'page arguments' => array(1, 3, 4), + 'access arguments' => array('use project browser'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Page callback: Allows for adding to and removing from the install queue. + * + * This is invoked via AJAX most of the time. + * + * @param string $method + * The method used for this callback. + * @param string $op + * The operation to perform. + * @param string $project_name + * The short name of the project. + * + * @return array + * An array of elements that should be changed. + * + * @see project_browser_menu() + */ +function project_browser_install_queue_callback($method, $op, $project_name) { + module_load_include('inc', 'project_browser', 'project_browser'); + + switch ($op) { + case 'add': + $projects = project_browser_get_listed_projects(); + + if (isset($projects[$project_name])) { + $project = $projects[$project_name]; + project_browser_install_queue_add($project); + } + else { + drupal_set_message(t('Error: The project was not found.'), 'error'); + } + break; + + case 'remove': + project_browser_install_queue_remove($project_name); + break; + } + + switch ($method) { + case 'nojs': + // Redirect to the page it came from. + $redirect = (isset($_GET['destination'])) ? $_GET['destination'] : 'admin/modules/project-browser'; + + drupal_goto($redirect); + break; + + case 'ajax': + $commands = array(); + + // Refresh the install queue. + $commands[] = ajax_command_replace('#project-browser-install-queue', project_browser_get_install_list()); + // Refresh the add to queue link. + $commands[] = ajax_command_replace('#add-to-queue-link-' . $project_name, project_browser_add_remove_queue_link($project_name)); + + return array('#type' => 'ajax', '#commands' => $commands); + break; + } +} + +/** + * Implements hook_menu_local_tasks_alter(). + */ +function project_browser_menu_local_tasks_alter(&$data, $router_item, $root_path) { + switch ($root_path) { + // This is used to put the 'Project Browser' action on the 'Modules' page. + case 'admin/modules': + // Unset the install theme page. + foreach ($data['actions']['output'] as $num => $item) { + if ($item['#link']['path'] == 'admin/modules/install') { + unset($data['actions']['output'][$num]); + } + } + $item = menu_get_item('admin/modules/project-browser/modules'); + if ($item['access']) { + $item['title'] = t('Install new modules'); + $data['actions']['output'][] = array( + '#theme' => 'menu_local_action', + '#link' => $item, + ); + } + break; + + // This is used to include the old 'Install a module' link + case 'admin/modules/project-browser': + case 'admin/modules/project-browser/modules': + case 'admin/modules/project-browser/themes': + $item = menu_get_item('admin/modules/install'); + if ($item['access']) { + $item['title'] = t('Install manually'); + $data['actions']['output'][] = array( + '#theme' => 'menu_local_action', + '#link' => $item, + ); + } + break; + + // This is used to put the 'Project Browser' action on the 'Appearance' + // page. + case 'admin/appearance': + // Unset the install theme page. + foreach ($data['actions']['output'] as $num => $item) { + if ($item['#link']['path'] == 'admin/appearance/install') { + unset($data['actions']['output'][$num]); + } + } + $item = menu_get_item('admin/modules/project-browser/themes'); + if ($item['access']) { + $item['title'] = t('Install new themes'); + $data['actions']['output'][] = array( + '#theme' => 'menu_local_action', + '#link' => $item, + ); + } + break; + } +} + +/** + * Implements hook_theme(). + */ +function project_browser_theme($existing, $type, $theme, $path) { + return array( + // Template for installation page. + 'project_browser_install' => array( + 'variables' => array('current_task' => NULL, 'main_content' => NULL), + 'path' => $path . '/theme', + 'template' => 'project-browser-install', + ), + // Template for list of projects. + 'project_browser_list' => array( + 'variables' => array('projects_list' => NULL, 'type' => NULL), + 'path' => $path . '/theme', + 'template' => 'project-browser-list', + ), + // Template for list of projects. + 'project_browser_block' => array( + 'render element' => 'element', + 'path' => $path . '/theme', + 'template' => 'project-browser-block', + ), + // Template for single project. + 'project_browser_project' => array( + 'variables' => array('project' => NULL, 'first' => NULL), + 'path' => $path . '/theme', + 'template' => 'project-browser-project', + ), + // Template for install queue item. + 'project_browser_install_queue' => array( + 'variables' => array('projects' => NULL), + 'path' => $path . '/theme', + 'template' => 'project-browser-install-queue', + ), + ); +} + +/** + * Implements hook_preprocess_HOOK() for project-browser-install.tpl.php. + * + * Adds some variables for the projects install theme. + * + * @param array $variables + * An associative array containing: + * - current_task: The current task. + * + * @ingroup themeable + */ +function project_browser_preprocess_project_browser_install(array &$variables) { + module_load_include('inc', 'project_browser', 'project_browser.pages'); + // Add the themed list + $variables['task_list'] = project_browser_installation_task_list($variables['current_task']); +} + +/** + * Implements hook_preprocess_HOOK() for project-browser-install-queue.tpl.php. + * + * Adds some variables for the projects install queue theme. + * + * @param array $variables + * An associative array containing: + * - projects: An array of projects in the install queue. + * + * @ingroup themeable + */ +function project_browser_preprocess_project_browser_install_queue(array &$variables) { + $build = array(); + if (empty($variables['projects'])) { + $build['empty_text'] = array( + '#markup' => t('Install queue is empty.'), + ); + } + else { + foreach ($variables['projects'] as $project) { + $build['queued-item-' . $project['name']] = array( + '#prefix' => "
", + '#markup' => project_browser_add_remove_queue_link($project['name'], $project['title'], 'remove-queue-link'), + '#suffix' => "
", + ); + } + $build['install-link'] = drupal_get_form('project_browser_install_button_form'); + } + + // Add the install button. + $variables['queue_html'] = drupal_render($build); +} + +/** + * Implements hook_preprocess_HOOK() for project-browser-block.tpl.php. + * + * Adds some variables for the project browser block theme. + * + * @param array $variables + * An associative array containing: + * - element['#title']: The title of the block. + * - element['#content']: The content of the block. + * + * @ingroup themeable + */ +function project_browser_preprocess_project_browser_block(array &$variables) { + // Add the title and content variables. + $variables['title'] = $variables['element']['#title']; + $variables['content'] = $variables['element']['#content']; +} + +/** + * Implements hook_preprocess_HOOK() for project-browser-list.tpl.php. + * + * Adds some variables for the projects list theme. + * + * @param array $variables + * An associative array containing: + * - projects_list: An array of all projects. + * + * @ingroup themeable + */ +function project_browser_preprocess_project_browser_list(array &$variables) { + module_load_include('inc', 'project_browser', 'project_browser'); + drupal_add_library('project_browser', 'drupal.project_browser.css'); + + if (is_array($variables['projects_list']) AND !empty($variables['projects_list'])) { + $content = ''; + $first = TRUE; + // Theme each individual project and add to the list. + foreach ($variables['projects_list'] as $project) { + $content .= theme('project_browser_project', array('project' => $project, 'first' => $first)); + $first = FALSE; + } + } + else { + $content = t('No results found.'); + } + + switch ($variables['type']) { + case 'module': + $title = t('Most used Drupal modules'); + break; + + case 'theme': + $title = t('Most used Drupal themes'); + break; + + default: + $title = t('Projects'); + break; + + } + + // If any filters were selected, change the title to 'Search results'. + if (!empty($_SESSION['project_browser_text_filter_' . $variables['type']]) || !empty($_SESSION['project_browser_category_filter_' . $variables['type']])) { + $title = t('Search results'); + } + + $main_content['project_browser_main_block'] = array( + '#theme' => 'project_browser_block', + '#title' => $title, + '#content' => $content, + '#contextual_links' => array( + 'project_browser' => array('admin/config/development/project_browser', array()), + ), + ); + $variables['main_content'] = render($main_content); + + // Add the pager. + $variables['pager'] = theme('pager', array('tags' => NULL)); + + // Add the filters. + $filters_form = drupal_get_form('project_browser_filters_form', $variables['type']); + $filters['project_browser_filters_block'] = array( + '#theme' => 'project_browser_block', + '#title' => t('Filters'), + '#content' => drupal_render($filters_form), + ); + $variables['filters'] = render($filters); + + // Add the install list. + $install_list['project_browser_filters_block'] = array( + '#theme' => 'project_browser_block', + '#title' => t('Install queue'), + '#content' => project_browser_get_install_list(), + ); + $variables['install_list'] = render($install_list); +} + +/** + * Implements hook_preprocess_HOOK() for project-browser-project.tpl.php. + * + * Adds some variables for the project theme. + * + * @param array $variables + * An associative array containing: + * - project: An associative array of project variables. + * + * @ingroup themeable + */ +function project_browser_preprocess_project_browser_project(array &$variables) { + module_load_include('inc', 'project_browser', 'project_browser'); + $project = $variables['project']; + + $variables['title'] = l($project['title'], check_url($project['project url']), + array('attributes' => array('target' => '_blank'), 'html' => TRUE)); + $variables['author'] = t('Author: @author', array('@author' => $project['author'])); + $variables['description'] = _filter_htmlcorrector(filter_xss($project['description'])); + $variables['image'] = $project['image']; + $variables['last_updated'] = ($project['last updated']) ? t('Last Updated: @date', array('@date' => format_date($project['last updated'], 'long'))) : ''; + + $extras = array(); + + if ($project['maintenance status']) { + $extras[] = check_plain($project['maintenance status']); + } + if ($project['development status']) { + // We are not showing this because it isn't a good indicator right now. + // $extras[] = check_plain($project['development status']); + } + if ($project['usage'] AND is_numeric($project['usage'])) { + $extras[] = format_plural($project['usage'], '1 Install', '@count Installs'); + } + if ($project['rating']) { + $extras[] = check_plain($project['rating']); + } + + $variables['extras'] = implode(' | ', $extras); + + // Check if the project is installed. + if (_project_browser_is_project_enabled($project['type'], $project['name'])) { + $variables['status'] = '
Already installed
'; + $variables['install'] = ''; + } + elseif (drupal_get_filename($project['type'], $project['name'])) { + $variables['status'] = '
Already downloaded
'; + $variables['install'] = ''; + } + else { + $variables['status'] = ''; + $variables['install'] = project_browser_add_remove_queue_link($project['name']); + } +} + +/** + * Builds the add/remove project to install queue link. + * + * @param string $project_name + * The short name of the project, such as 'views'. + * @param string|null $title + * (optional) The title of the project. Defaults to NULL. + * @param string $id_prefix + * (optional) The prefix that should be prepended to the id, to ensure unique + * id names. Defaults to 'add-to-queue-link'. + * + * @return string + * A themed link to remove or add an item from the install queue. + */ +function project_browser_add_remove_queue_link($project_name, $title = NULL, $id_prefix = 'add-to-queue-link') { + $queued_projects = project_browser_get_queued_projects(); + if (!$title) { + $title = isset($queued_projects[$project_name]) ? t('Remove from Install queue') : t('Add to Install queue'); + } + $op = isset($queued_projects[$project_name]) ? 'remove' : 'add'; + + $build['ajax_link'] = array( + '#type' => 'link', + '#title' => $title, + '#href' => 'project-browser/nojs/install-queue/' . $op . '/'. $project_name, + '#options' => array( + 'query' => drupal_get_destination(), + ), + '#id' => $id_prefix . '-' . $project_name, + '#ajax' => array( + 'effect' => 'fade', + 'speed' => 1000, + 'progress' => array( + 'type' => 'throbber', + 'message' => '', + ), + ), + ); + + return drupal_render($build); +} + +/** + * Implements hook_library_info(). + */ +function project_browser_library_info() { + $libraries['drupal.project_browser'] = array( + 'title' => 'Project Browser', + 'version' => VERSION, + 'js' => array( + drupal_get_path('module', 'project_browser') . '/js/project_browser_more_link.js' => array(), + ), + 'dependencies' => array( + array('system', 'jquery'), + array('system', 'drupal'), + array('project_browser', 'drupal.project_browser.css'), + ), + ); + $libraries['drupal.project_browser.select_releases'] = array( + 'title' => 'Project Browser Select Releases', + 'version' => VERSION, + 'js' => array( + drupal_get_path('module', 'project_browser') . '/js/project_browser_select_releases.js' => array(), + ), + 'dependencies' => array( + array('system', 'jquery'), + array('system', 'drupal'), + array('project_browser', 'drupal.project_browser.css'), + ), + ); + $libraries['drupal.project_browser.css'] = array( + 'title' => 'Project Browser CSS', + 'version' => VERSION, + 'css' => array( + drupal_get_path('module', 'project_browser') . '/css/project_browser.admin.css' => array( + 'type' => 'file', + 'media' => 'screen', + ), + ), + ); + return $libraries; +} diff --git a/core/modules/project_browser/project_browser.pages.inc b/core/modules/project_browser/project_browser.pages.inc new file mode 100644 index 0000000..0c71028 --- /dev/null +++ b/core/modules/project_browser/project_browser.pages.inc @@ -0,0 +1,565 @@ + $drupal_version[0], + 'type' => $type, + ); + + // Add filters. + if (isset($_SESSION['project_browser_category_filter_' . $type])) { + $categories = array_filter($_SESSION['project_browser_category_filter_' . $type]); + if (!empty($categories)) { + $filters['categories'] = project_browser_prepare_categories($categories, $type); + } + } + if (isset($_SESSION['project_browser_text_filter_' . $type])) { + $filters['text'] = $_SESSION['project_browser_text_filter_' . $type]; + } + if (isset($_SESSION['project_browser_order_by_filter_' . $type])) { + $filters['order_by'] = $_SESSION['project_browser_order_by_filter_' . $type]; + } + if (isset($_SESSION['project_browser_sort_filter_' . $type])) { + $filters['sort'] = $_SESSION['project_browser_sort_filter_' . $type]; + } + if (isset($_SESSION['project_browser_server_filter'])) { + $filters['server'] = $_SESSION['project_browser_server_filter']; + } + else { + $filters['server'] = 0; + } + $filters['requested'] = 10; + $filters['page'] = isset($_GET['page']) ? $_GET['page'] : 0; + + // Get the projects to display here based on the filters. + $results = project_browser_fetch_results($filters); + + // Save the listed projects in the session so it can be used. + $_SESSION['project_browser_listed_projects'] = $results['projects']; + + $test = project_browser_get_listed_projects(); + + $list = array(); + foreach ($results['projects'] as $project) { + $list[] = $project; + } + + // Add the pager. + $total = $results['total']; + $num_per_page = 10; + $page = pager_default_initialize($total, $num_per_page); + $offset = $num_per_page * $page; + $start = ($total) ? $offset + 1 : 0; + $finish = $offset + $num_per_page; + if ($finish > $total) { + $finish = $total; + } + + $sort_options = project_browser_get_sort_options(); + $current_order_by = isset($_SESSION['project_browser_order_by_filter_' . $type]) ? $_SESSION['project_browser_order_by_filter_' . $type] : 'score'; + $current_sort = isset($_SESSION['project_browser_sort_filter_' . $type]) ? $_SESSION['project_browser_sort_filter_' . $type] : 'desc'; + + $build = array(); + $build['content'] = array( + 'project_browser_header' => array( + '#markup' => t('Showing @start to @finish of @total.', array( + '@start' => $start, '@finish' => $finish, '@total' => $total)), + '#weight' => 0, + ), + 'project_browser_sort_header' => array( + '#type' => 'item', + '#weight' => 2, + '#markup' => project_browser_get_sort_widget($sort_options, $current_order_by, $current_sort), + ), + 'project_browser_list' => array( + '#markup' => theme('project_browser_list', array('projects_list' => $list, 'type' => $type)), + '#weight' => 3, + ), + 'pager' => array( + '#theme' => 'pager', + '#weight' => 99, + ), + ); + + $servers = project_browser_get_servers(); + + if (count($servers) > 1) { + $build['content']['project_browser_server_header'] = array( + '#type' => 'item', + '#weight' => 1, + '#markup' => project_browser_get_server_widget($servers, $filters['server']), + ); + } + + $build['#attached']['library'][] = array( + 'project_browser', + 'drupal.project_browser' + ); + + return $build; +} + +/** + * Builds a page from the install process. + * + * @param string $op + * Operation to perform. + * + * @return string + * A themed page from the install process, depending on the $op. + * + * @see project_browser_menu() + */ +function project_browser_installation_page($op) { + drupal_add_library('project_browser', 'drupal.project_browser.css'); + + switch ($op) { + case 'select_versions': + drupal_set_title(t("Select versions")); + $content = project_browser_installation_select_versions_page(); + break; + + case 'install_dependencies': + drupal_set_title(t("Install Dependencies")); + $content = project_browser_installation_install_dependencies_page(); + break; + + case 'enable': + drupal_set_title(t("Enable modules")); + $content = project_browser_installation_enable_page(); + break; + } + return theme('project_browser_install', array('current_task' => $op, 'main_content' => drupal_render($content))); +} + +/** + * Page callback: Shows the Select versions installation task. + * + * Shows a form where the user can select which versions to install for each + * project. + * + * @return array + * The form to select the versions of the projects the user wants to install. + * + * @see project_browser_menu() + */ +function project_browser_installation_select_versions_page() { + module_load_include('inc', 'project_browser', 'project_browser'); + // Show a form that lets the user select which version of the projects to + // install. + $queued_projects = project_browser_get_queued_projects(); + unset($_SESSION['project_browser_installed_projects']); + + return drupal_get_form('project_browser_installation_select_versions_form', $queued_projects); +} + +/** + * Form constructor for the select versions form. + * + * @param array $projects + * An array of projects to get the releases for. + * + * @see project_browser_installation_select_versions_form_submit() + * @ingroup forms + */ +function project_browser_installation_select_versions_form($form, &$form_state, array $projects) { + module_load_include('inc', 'project_browser', 'project_browser'); + drupal_add_library('project_browser', 'drupal.project_browser.select_releases'); + + $form = array(); + + // First unset any old data. + unset($_SESSION['project_browser_install_releases_list']); + + $form['#tree'] = TRUE; + + $form['releases-header'] = array( + '#type' => 'item', + '#markup' => t("You're about to install:"), + ); + + $form['releases'] = array(); + + foreach ($projects as $project) { + // Get the available releases for this project. + if (!$release_data = project_browser_get_project_release_data($project)) { + drupal_set_message(t('Could not fetch releases for project %project.', + array('%project' => $project['title'])), 'warning'); + watchdog('project_browser', 'Could not fetch releases for project %project.', + array('%project' => $project['title']), WATCHDOG_ERROR); + project_browser_install_queue_remove($project['name']); + continue; + } + + // We use the update module to calculate the recommended version. + $project_data = array( + 'existing_major' => 0, + 'existing_version' => 0, + 'install_type' => '', + ); + module_load_include('inc', 'update', 'update.compare'); + update_calculate_project_update_status($project_data, $release_data); + + $releases_list = array(); + + foreach ($release_data['releases'] as $version => $release) { + $release_title = t("@project @version - @date", array( + '@project' => $project['title'], + '@version' => $release['version'], + '@date' => format_date($release['date'], 'custom', 'M j, Y'), + )); + if (isset($release['terms']['Release type']) AND !empty($release['terms']['Release type'])) { + $release_title .= " (" . implode(', ', $release['terms']['Release type']) . ")"; + } + if (isset($release['release_link'])) { + $releases_list[$version] = l($release_title, $release['release_link']); + } + else { + $releases_list[$version] = $release_title; + } + } + + $form['releases'][$project['name']]['project'] = array( + '#type' => 'value', + '#value' => $project, + ); + + $form['releases'][$project['name']]['release_name'] = array( + '#type' => 'radios', + '#title' => t('Select release for @project', array('@project' => $project['title'])), + '#options' => $releases_list, + '#default_value' => key($releases_list), + '#prefix' => '
', + '#suffix' => '
', + '#attributes' => array( + 'class' => array('project-browser-releases-radios'), + ), + '#required' => TRUE, + ); + $form['releases'][$project['name']]['selected_text'] = array( + '#type' => 'item', + '#prefix' => '
', + '#suffix' => '
', + '#markup' => reset($releases_list), + ); + if (isset($project_data['recommended'])) { + // If there is a recommended release set, then only show it and show the + // jQuery link. + $recommended_releases = array(); + $recommended_releases[$project_data['recommended']] = $releases_list[$project_data['recommended']]; + $form['releases'][$project['name']]['release_name']['#default_value'] = $project_data['recommended']; + $form['releases'][$project['name']]['selected_text']['#markup'] = $releases_list[$project_data['recommended']]; + } + if (count($releases_list) > 1) { + $form['releases'][$project['name']]['selected_text']['#markup'] .= + " (" . t('change release') . ")"; + } + } + + // If there is nothing to install, go to the enable page. + if (empty($form['releases'])) { + drupal_set_message(t('No releases data found for any of the selected projects.'), 'warning'); + drupal_goto('admin/modules/project-browser/install/enable'); + } + + $form['backup_warning'] = array( + '#type' => 'markup', + '#markup' => t('Back up your database and site before you continue. !link.', + array('!link' => l(t('Learn how'), 'http://drupal.org/node/22281'))), + ); + $form['maintenance_mode'] = array( + '#type' => 'checkbox', + '#title' => t('Perform updates with site in maintenance mode (strongly recommended)'), + '#default_value' => TRUE, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Install'), + ); + + return $form; +} + +/** + * Form submission handler for project_browser_installation_select_versions_form(). + * + * This sets the batch to install the different selected releases one by one. + */ +function project_browser_installation_select_versions_form_submit($form, &$form_state) { + module_load_include('inc', 'project_browser', 'project_browser'); + // Store maintenance_mode setting so we can restore it when done. + $_SESSION['maintenance_mode'] = config('system.maintenance')->get('enabled'); + if ($form_state['values']['maintenance_mode'] == TRUE) { + config('system.maintenance')->set('enabled', TRUE)->save(); + } + + foreach ($form_state['values']['releases'] as $item) { + // Load the selected release. + if ($release = project_browser_get_release($item['release_name'], $item['project'])) { + // Add the release to a session variable. + $_SESSION['project_browser_install_releases_list'][$item['project']['name']] = array( + 'release_name' => $item['release_name'], + 'project' => $item['project'], + ); + } + } + + // Install the projects with batch. + module_load_include('inc', 'update', 'update.manager'); + + $queued_releases = project_browser_get_queued_releases(); + + $operations = array(); + foreach ($queued_releases as $short_name => $info) { + $operations[] = array('_project_browser_batch_install_release', array($info['release_name'], $info['project'])); + } + $batch = array( + 'operations' => $operations, + 'finished' => '_project_browser_batch_install_releases_finished', + 'title' => t('Installing projects'), + 'init_message' => t('Installing modules...'), + 'progress_message' => t('Installed @current out of @total.'), + 'error_message' => t('Installation has encountered an error.'), + 'file' => drupal_get_path('module', 'project_browser') . '/project_browser.inc', + ); + batch_set($batch); +} + +/** + * Page callback: Handles the Install Dependencies installation task. + * + * This shows a form which lets the user select which version of dependencies + * to install. This is only shown if there are missing dependencies. If there + * are no missing dependencies, then we redirect to the enable page. + * + * @return array + * The form array to let the user select the dependencies to install. + * + * @see project_browser_menu() + */ +function project_browser_installation_install_dependencies_page() { + module_load_include('inc', 'project_browser', 'project_browser'); + $projects = project_browser_get_installed_projects(); + $missing = project_browser_get_missing_dependencies($projects); + + if (count($missing) > 0) { + $missing_projects = array(); + // Add the project data in the array as best we can. + foreach ($missing as $project_shortname => $dependencies) { + foreach ($dependencies as $shortname) { + $missing_projects[$shortname] = array( + 'name' => $shortname, + // Missing dependencies only works for projects of type 'module' + 'type' => 'module', + 'title' => $shortname, + ); + } + } + + return drupal_get_form('project_browser_installation_select_versions_form', $missing_projects); + } + else { + drupal_goto('admin/modules/project-browser/install/enable'); + } +} + +/** + * Page callback: Shows the options for the Enable projects installation task. + * + * This shows a form which lets the user enable the newly installed projects. If + * there are unresolved dependencies, then the project is shown with a message + * about why it can't be enabled. This redirects to the project-browser page if + * there were no installed projects. + * + * @return array + * The form array to enable projects, or redirect to + * 'admin/modules/project-browser'. + * + * @see project_browser_menu() + */ +function project_browser_installation_enable_page() { + module_load_include('inc', 'project_browser', 'project_browser'); + $installed_projects = project_browser_get_installed_projects(); + + if (count($installed_projects) > 0) { + return drupal_get_form('project_browser_installation_enable_form', $installed_projects); + } + else { + drupal_goto('admin/modules/project-browser'); + } +} + +/** + * Form constructor for the enable projects form. + * + * @param array $projects + * An array of newly installed projects to enable. + * + * @see project_browser_installation_enable_form_submit() + * @ingroup forms + */ +function project_browser_installation_enable_form($form, &$form_state, array $projects) { + $modules = system_rebuild_module_data(); + $form['instructions'] = array( + '#type' => 'item', + '#markup' => t('The projects you selected have been successfully installed. + If you installed any new modules, you may enable them using the form below + or on the main !link page.', array('!link' => l(t('Modules'), 'admin/modules'))), + ); + + $options = array(); + $missing = array(); + + foreach ($projects as $project) { + if ($project['type'] == 'module') { + $dependency_check = TRUE; + $dependencies = array(); + if (isset($modules[$project['name']])) { + foreach ($modules[$project['name']]->info['dependencies'] as $dependency) { + if (isset($modules[$dependency])) { + $dependencies[] = $modules[$dependency]->info['name'] . ' (' . t('Installed') . ')'; + } + else { + $dependency_check = FALSE; + $dependencies[] = $dependency . ' (' . t('Missing') . ')'; + } + } + if ($dependency_check) { + $options[$project['name']] = array( + array('data' => $modules[$project['name']]->info['name']), + array('data' => $modules[$project['name']]->info['version']), + array('data' => implode(', ', $dependencies)), + ); + } + else { + $missing[$project['name']] = array( + array('data' => $modules[$project['name']]->info['name']), + array('data' => $modules[$project['name']]->info['version']), + array('data' => implode(', ', $dependencies)), + ); + } + } + else { + drupal_set_message(t('There was an error getting information for @module', + array('@module' => $project['name'])), 'error'); + } + } + } + + $headers = array( + array('data' => t('Title')), + array('data' => t('Version')), + array('data' => t('Dependencies')), + ); + + if (!empty($options)) { + $form['modules'] = array( + '#type' => 'tableselect', + '#title' => t('Enable modules'), + '#description' => t('Select which modules you would like to enable.'), + '#header' => $headers, + '#options' => $options, + '#empty' => t('No new modules installed.'), + '#multiple' => TRUE, + '#js_select' => TRUE, + '#weight' => 1, + ); + + $form['submit'] = array( + '#type' => 'submit', + '#submit' => array('project_browser_installation_enable_form_submit'), + '#value' => t('Enable modules'), + '#weight' => 99, + ); + } + + if (!empty($missing)) { + $form['missing'] = array( + '#type' => 'item', + '#title' => t('Missing Dependencies'), + '#description' => t('These modules are missing one or more dependencies, + and so cannot be enabled.'), + '#markup' => theme('table', array('header' => $headers, 'rows' => $missing)), + '#weight' => 2, + ); + } + + return $form; +} + +/** + * Form submission handler for project_browser_installation_enable_form(). + * + * Enables the selected projects from the enable projects form. After the + * selected projects are enabled, we flush all caches and then redirect to the + * modules page. + */ +function project_browser_installation_enable_form_submit($form, &$form_state) { + $enable_queue = array_filter($form_state['values']['modules']); + // Enable these all at once so that dependencies are handled properly. + module_enable($enable_queue); + + drupal_flush_all_caches(); + + drupal_goto('admin/modules'); +} + +/** + * Builds a task list to the sidebar area when installing projects. + * + * This will need to be called from every page of the install process. + * + * @param string $active + * (optional) Set the active task by key. Defaults to NULL. + * + * @return array + * The themed task list for the install projects process. + */ +function project_browser_installation_task_list($active = NULL) { + // Default list of tasks. + $tasks = array( + 'select_versions' => t('Select versions'), + 'install_dependencies' => t('Install Dependencies'), + 'enable' => t('Enable projects'), + ); + + require_once DRUPAL_ROOT . '/core/includes/theme.maintenance.inc'; + + return theme_task_list(array('items' => $tasks, 'active' => $active)); +} diff --git a/core/modules/project_browser/tests/project_browser_test.info b/core/modules/project_browser/tests/project_browser_test.info new file mode 100644 index 0000000..543e1d0 --- /dev/null +++ b/core/modules/project_browser/tests/project_browser_test.info @@ -0,0 +1,6 @@ +name = Project Browser module tests +description = Support module for Project Browser related testing. +package = Testing +version = VERSION +core = 8.x +hidden = TRUE diff --git a/core/modules/project_browser/tests/project_browser_test.module b/core/modules/project_browser/tests/project_browser_test.module new file mode 100644 index 0000000..46b456f --- /dev/null +++ b/core/modules/project_browser/tests/project_browser_test.module @@ -0,0 +1,343 @@ + 'Test Query page', + 'description' => "Tests the ability to fetch and display projects, and filter them appropriately.", + 'page callback' => 'project_browser_test_query', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Page callback: Generates json based on the input filters. + * + * @see project_browser_test_menu() + */ +function project_browser_test_query() { + if (!isset($_GET['method'])) { + print drupal_json_encode(t('You must specify a method.')); + exit(); + } + switch ($_GET['method']) { + case 'categories': + if (!isset($_GET['type'])) { + print drupal_json_encode(t('You must specify a project type.')); + exit(); + } + + $categories = project_browser_test_get_categories($_GET['type']); + + print drupal_json_encode($categories); + exit(); + + case 'query': + // Check that we have valid data. + if (!isset($_GET['version'])) { + print drupal_json_encode(t('You must specify a drupal version.')); + exit(); + } + if (!isset($_GET['type'])) { + print drupal_json_encode(t('You must specify a project type.')); + exit(); + } + + // Get the filters. + $filters = array( + 'drupal_version' => $_GET['version'], + 'type' => $_GET['type'], + 'text' => (isset($_GET['text']) AND $_GET['text']) ? $_GET['text'] : '', + 'sort_method' => isset($_GET['sort_method']) ? $_GET['sort_method'] : 'usage', + 'sort_direction' => isset($_GET['sort_direction']) ? $_GET['sort_direction'] : 'desc', + 'requested' => isset($_GET['requested']) ? (int) $_GET['requested'] : 12, + 'page' => isset($_GET['page']) ? (int) $_GET['page'] : 0, + ); + + if (isset($_GET['categories'])) { + $categories = unserialize($_GET['categories']); + if (is_array($categories) AND !empty($categories)) { + $filters['categories'] = $categories; + } + } + + // Pass them off to the project_browser_server_get_results() function. + $results = project_browser_test_get_results($filters); + + print drupal_json_encode($results); + + exit(); + } +} + +/** + * Returns an array of static categories to use for testing. + * + * @param string $type + * The type of project, 'module' or 'theme'. + * + * @return array + * An array of categories for the project type. + */ +function project_browser_test_get_categories($type) { + $categories = array(); + + switch ($type) { + case 'module': + $categories = array( + 'admin' => "Administrative", + 'search' => "Search", + 'user_management' => "User Management", + ); + break; + + case 'theme': + $categories = array( + 'dark' => "Dark", + 'light' => "Light", + ); + } + + return $categories; +} + +/** + * Returns an array of filtered projects. + * + * @param array $filters + * An array of filters to use. + * + * @return array + * An array of projects. + */ +function project_browser_test_get_results($filters) { + $projects = project_browser_test_projects(); + + $results = array( + 'total' => count($projects), + 'projects' => array(), + ); + + // Filter out projects based on type. + if (isset($filters['type']) AND $type = $filters['type']) { + foreach ($projects as $name => $project) { + if ($type != $project['type']) { + unset($projects[$name]); + } + } + } + + // Filter out projects based on drupal version number. + if (isset($filters['drupal_version']) AND $version = $filters['drupal_version']) { + foreach ($projects as $name => $project) { + if ($version != $project['drupal version']) { + unset($projects[$name]); + } + } + } + + // Filter out projects based on categories number. + if (isset($filters['categories']) AND is_array($filters['categories']) AND !empty($filters['categories'])) { + $filtered = array(); + foreach ($projects as $name => $project) { + foreach ($project['categories'] as $category) { + if (in_array($category, $filters['categories'])) { + $filtered[$name] = $project; + } + } + } + $projects = $filtered; + } + + // Filter out projects based on the text query. + if (isset($filters['text']) AND $text = $filters['text']) { + foreach ($projects as $name => $project) { + if (!stristr($project['title'], $text) AND !stristr($project['description'], $text)) { + unset($projects[$name]); + } + } + } + + $results['total'] = count($projects); + + // Only send back the requested amount. + $start = $filters['page'] * $filters['requested']; + $end = $start + $filters['requested']; + + $results['projects'] = $projects; + + return $results; +} + +/** + * Returns an array of projects to use for testing. + * + * @return array + * An array of projects. + */ +function project_browser_test_projects() { + $projects = array(); + + $projects['views'] = array( + 'type' => 'module', + 'title' => 'Views', + 'name' => 'views', + 'drupal version' => 8, + 'author' => 'merlinofchaos', + 'description' => "The Views module provides a flexible method for Drupal site + designers to control how lists and tables of content (nodes in Views 1, almost + anything in Views 2) are presented. Traditionally, Drupal has hard-coded most of + this, particularly in how taxonomy and tracker lists are formatted. ", + 'categories' => array('admin', 'search'), + 'image' => 'http://learnbythedrop.com/system/files/images/View-Edit_0.png', + 'usage' => '542312', + 'project url' => 'http://www.drupal.org/projects/views', + 'project status url' => 'http://updates.drupal.org/release-history/views/7.x', + 'last updated' => '12342523', + 'maintenance status' => 'Actively maintained', + 'development status' => 'Under active development', + 'rating' => '9.6', + 'dependencies' => array( + 'ctools', + ), + ); + + $projects['ctools_test'] = array( + 'type' => 'module', + 'title' => 'CTools Test', + 'name' => 'ctools_test', + 'drupal version' => 8, + 'author' => 'merlinofchaos', + 'description' => "This suite is primarily a set of APIs and tools to improve + the developer experience. It also contains a module called the Page Manager + whose job is to manage pages. In particular it manages panel pages, but as + it grows it will be able to manage far more than just Panels.", + 'categories' => array(), + 'image' => '', + 'usage' => '4312', + 'project url' => 'http://www.drupal.org/projects/ctools', + 'project status url' => 'http://updates.drupal.org/release-history/ctools/7.x', + 'last updated' => '12354634', + 'maintenance status' => 'Actively maintained', + 'development status' => 'Under active development', + 'rating' => '7.6', + 'dependencies' => array(), + ); + + $projects['ctools'] = array( + 'type' => 'module', + 'title' => 'Chaos Tool Suite', + 'name' => 'ctools', + 'drupal version' => 8, + 'author' => 'merlinofchaos', + 'description' => "This suite is primarily a set of APIs and tools to improve + the developer experience. It also contains a module called the Page Manager + whose job is to manage pages. In particular it manages panel pages, but as + it grows it will be able to manage far more than just Panels.", + 'categories' => array(), + 'image' => '', + 'usage' => '4312', + 'project url' => 'http://www.drupal.org/projects/ctools', + 'project status url' => 'http://updates.drupal.org/release-history/ctools/7.x', + 'last updated' => '12354634', + 'maintenance status' => 'Actively maintained', + 'development status' => 'Under active development', + 'rating' => '7.6', + 'dependencies' => array(), + ); + + $projects['token'] = array( + 'type' => 'module', + 'title' => 'Token', + 'name' => 'token', + 'drupal version' => 8, + 'author' => 'eaton', + 'description' => "Tokens are small bits of text that can be placed into larger + documents via simple placeholders, like %site-name or [user]. The Token module + provides a central API for modules to use these tokens, and expose their own token values.", + 'categories' => array('admin'), + 'image' => 'http://drupal.org/files/images/token_08.thumbnail.png', + 'usage' => '4563', + 'project url' => 'http://www.drupal.org/projects/token', + 'project status url' => 'http://updates.drupal.org/release-history/token/7.x', + 'last updated' => '12357351', + 'maintenance status' => 'Actively maintained', + 'development status' => 'Under active development', + 'rating' => '8.1', + 'dependencies' => array(), + ); + + $projects['zen'] = array( + 'type' => 'theme', + 'title' => 'Zen', + 'name' => 'zen', + 'drupal version' => 8, + 'author' => 'johnAlbin', + 'description' => "Zen is the ultimate starting theme for Drupal. If you are + building your own standards-compliant theme, you will find it much easier to + start with Zen than to start with Garland or Bluemarine. This theme has fantastic + online documentation and tons of code comments for both the PHP (template.php) + and HTML (page.tpl.php, node.tpl.php).", + 'categories' => array('light', 'dark'), + 'image' => 'http://drupal.org/files/images/zen-logo.thumbnail.png', + 'usage' => '4563', + 'project url' => 'http://www.drupal.org/project/zen', + 'project status url' => 'http://updates.drupal.org/release-history/zen/7.x', + 'last updated' => '12343634', + 'maintenance status' => 'Actively maintained', + 'development status' => 'Under active development', + 'rating' => '7.1', + 'dependencies' => array(), + ); + + $projects['acquia_marina'] = array( + 'type' => 'theme', + 'title' => 'Acquia Marina', + 'name' => 'acquia_marina', + 'drupal version' => 8, + 'author' => 'stephthegeek', + 'description' => "The Fusion base theme and Skinr are required. Skinr for Drupal 7 + (dev release) is usable now but it is recommended that you proceed with caution + and do some of your own testing.", + 'categories' => array('light'), + 'image' => 'http://drupal.org/files/images/acquia_marina.thumbnail.png', + 'usage' => '14563', + 'project url' => 'http://www.drupal.org/project/acquia_marina', + 'project status url' => 'http://updates.drupal.org/release-history/acquia_marina/7.x', + 'last updated' => '12346574', + 'maintenance status' => 'Actively maintained', + 'development status' => 'Under active development', + 'rating' => '7.8', + 'dependencies' => array( + 'fusion' + ), + ); + + $projects['fusion'] = array( + 'type' => 'theme', + 'title' => 'Fusion', + 'name' => 'fusion', + 'drupal version' => 8, + 'author' => 'stephthegeek', + 'description' => "Fusion is a powerful base theme, with layout and style configuration + options built in that you can control through Drupal's UI. It's based on a simplified + 960px or fluid 12/16-column grid. It's designed to be used with the Skinr module, + with numerous useful block styles included.", + 'categories' => array('light'), + 'image' => 'http://drupal.org/files/images/fusion-powering-small-banner.thumbnail.png', + 'usage' => '14563', + 'project url' => 'http://www.drupal.org/project/fusion', + 'project status url' => 'http://updates.drupal.org/release-history/fusion/7.x', + 'last updated' => '12342643', + 'maintenance status' => 'Actively maintained', + 'development status' => 'Under active development', + 'rating' => '', + 'dependencies' => array(), + ); + + return $projects; +} diff --git a/core/modules/project_browser/theme/project-browser-block.tpl.php b/core/modules/project_browser/theme/project-browser-block.tpl.php new file mode 100644 index 0000000..e5286dd --- /dev/null +++ b/core/modules/project_browser/theme/project-browser-block.tpl.php @@ -0,0 +1,21 @@ + +
+ +

+ +
+ +
+
diff --git a/core/modules/project_browser/theme/project-browser-install-queue.tpl.php b/core/modules/project_browser/theme/project-browser-install-queue.tpl.php new file mode 100644 index 0000000..4d611ea --- /dev/null +++ b/core/modules/project_browser/theme/project-browser-install-queue.tpl.php @@ -0,0 +1,15 @@ + +
+ +
diff --git a/core/modules/project_browser/theme/project-browser-install.tpl.php b/core/modules/project_browser/theme/project-browser-install.tpl.php new file mode 100644 index 0000000..98abc9c --- /dev/null +++ b/core/modules/project_browser/theme/project-browser-install.tpl.php @@ -0,0 +1,21 @@ + + +
+ +
+
+ +
diff --git a/core/modules/project_browser/theme/project-browser-list.tpl.php b/core/modules/project_browser/theme/project-browser-list.tpl.php new file mode 100644 index 0000000..6e0132f --- /dev/null +++ b/core/modules/project_browser/theme/project-browser-list.tpl.php @@ -0,0 +1,24 @@ + +
+
+ +
+
+ + +
+
diff --git a/core/modules/project_browser/theme/project-browser-project.tpl.php b/core/modules/project_browser/theme/project-browser-project.tpl.php new file mode 100644 index 0000000..3d5f484 --- /dev/null +++ b/core/modules/project_browser/theme/project-browser-project.tpl.php @@ -0,0 +1,58 @@ + +
+ +
+ +
+ + +
+

+ +

+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ +
+ +
+ + +
+
+
-- 1.7.3.1.msysgit.0