Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ed484c5
Progress so far on download functions
yorkshire-pudding Jun 27, 2024
5e6a710
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Jun 27, 2024
372537c
Further download changes to date
yorkshire-pudding Jul 2, 2024
38b3126
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Jul 2, 2024
5e4d555
Add info.inc with copies of Backdrops info file
yorkshire-pudding Jul 2, 2024
710a208
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Jul 4, 2024
6dfe8a0
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Jul 15, 2024
cf88998
Merge 1.x-1.x
yorkshire-pudding Jul 15, 2024
b1a9e04
progress to date
yorkshire-pudding Jul 24, 2024
e81c82e
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Jul 27, 2024
9e2d9f2
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Sep 2, 2024
12e094f
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Sep 6, 2024
a5f7567
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Sep 16, 2024
b6ab77f
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Oct 2, 2024
d6d285b
Download command enhancements
yorkshire-pudding Oct 2, 2024
063b1e7
Change bee_message to bee_instant_message where appropriate.
yorkshire-pudding Oct 4, 2024
54cfa45
Progress to date
yorkshire-pudding Oct 19, 2024
9b2b2e7
Allow cancel from select menu. Handle negative choices. Adjust tests.
yorkshire-pudding Apr 17, 2025
9340748
Update the Changelog with changes added elsewhere.
yorkshire-pudding Apr 17, 2025
360f052
Move check for exsting project before determine which version to down…
yorkshire-pudding Apr 19, 2025
619c25b
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Apr 19, 2025
061ecb4
Clarify changelog
yorkshire-pudding Apr 19, 2025
91f79d0
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Apr 19, 2025
ec2857c
Add tests for new options. Add release date to message for dl-core
yorkshire-pudding Apr 19, 2025
e108b14
Fix typo
yorkshire-pudding Apr 19, 2025
ff136fd
Fixed missing part of argument description for branch
yorkshire-pudding Apr 21, 2025
68abc28
Merge branch '1.x-1.x' into issue-348-download-project-enhance
yorkshire-pudding Apr 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .lando/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ clean_up() {
cd /app

# Remove Backdrop installations.
rm -rf backdrop/ multisite/ current/ directory/
rm -rf backdrop/ multisite/ current/ directory/ defined_release/

# Drop databases.
mysql -h database -u root -e "DROP DATABASE backdrop;"
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ and this project follows the
which is based on the major version of Backdrop CMS with a semantic version
system for each contributed module, theme and layout.

## [Unreleased] - 2024-10-10
## [Unreleased] - 2025-04-17

### Added
- An option for the `db-import` command to allow import from newer MariaDB
servers with the enable sandbox command in the dump file if the destination
database or client does not support it.
- Tooling for the lando recipe to support Xdebug with VS Code.
- The ability to download specified releases or branches of modules, themes,
layout templates or Backdrop itself.

### Fixed
- Unhandled errors and warnings if commands run outside Backdrop root and/or
Expand All @@ -23,6 +25,8 @@ before installing Backdrop.
### Changed
- Bee will now notify the user of additional modules that will be enabled or disabled
based on module dependencies when using the enable and disable commands.
- The functions within the download command have been made more flexible and
better able to support the coming 'update' command.

## [1.x-1.1.0] - 2024-09-07

Expand Down
1 change: 1 addition & 0 deletions bee.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
require_once __DIR__ . '/includes/filesystem.inc';
require_once __DIR__ . '/includes/input.inc';
require_once __DIR__ . '/includes/globals.inc';
require_once __DIR__ . '/includes/info.inc';
require_once __DIR__ . '/includes/telemetry.inc';
require_once __DIR__ . '/includes/dependencies.inc';

Expand Down
1,127 changes: 841 additions & 286 deletions commands/download.bee.inc

Large diffs are not rendered by default.

38 changes: 29 additions & 9 deletions docs/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,33 @@ options in a given file.
- `bee projects` - Show information about all available projects.

#### `download`
*Description:* Download Backdrop contrib projects together with dependencies.
*Aliases:* `dl` , `pm-download`
*Description:* Download Backdrop contrib projects.
*Aliases:* `dl`, `pm-download`
*Arguments:*
- `projects` - One or more contrib projects to download.
- `projects` - One or more contrib projects to download. You can specify a release tag or branch for each project using the format:
project:release_tag/keyword[:branch]

Specify a release tag or one of the following keywords:
- 'dev' (download the dev version from the default branch.)
- 'branch' (download the dev version from an alternative branch. Specified with `:branch_name`.)
- 'select' (a list of valid options will be offered including dev and all releases that are not draft. Latest and pre-releases will be labelled.)

If 'branch' is entered for release, then the name of an alternative branch can be entered with this option. It is ignored otherwise.

By default, 'latest' is selected if no release is specified. Dependencies will all download the 'latest' so if you do want a different version, download these first.

*Options:*
- `--github-token` - A Github Personal Access Token (Classic) that can be used to extend the GitHub API rate.
- `--hide-progress`, `-h` - Deprecated, will get removed in a future version.
- `--allow-multisite-copy`, `-f` - Override the check that would prevent the project being downloaded to a multisite site if the project exists in the shared project directory.
- `--github-token=THE TOKEN.` - A Github Personal Access Token (Classic) that can be used to extend the GitHub API rate.

*Examples:*
- `bee download webform` - Download the Webform module.
- `bee download webform` - Download the latest version of the Webform module.
- `bee download simplify thesis bamboo` - Download the Simplify module, Thesis theme, and Bamboo layout.
- `bee --site=site_name download simplify --allow-multisite-copy` - Download an additional copy of the Simplify module into the site_name multisite module folder.
- `bee download simplify:dev` - Download the dev version of the Simplify module.
- `bee download paragraphs:branch:1.x-1.2` - Download the 1.x-1.2 branch of the Paragraphs module
- `bee download simplify:1.x-1.2.4` - Download the 1.x-1.2.4 release of the Simplify module
- `bee download simplify:select` - Select from a list of releases and the dev version for the Simplify module.

#### `enable`
*Description:* Enable one or more projects (modules, themes, layouts).
Expand Down Expand Up @@ -259,13 +272,20 @@ options in a given file.
*Aliases:* `dl-core`
*Arguments:*
- `directory` - (optional) The directory to download and extract Backdrop into. Leave blank to use the current directory.

*Options:*
- `--github-token` - A Github Personal Access Token (Classic) that can be used to extend the GitHub API rate.
- `--hide-progress`, `-h` - Deprecated, will get removed in a future version.
- `--version=THE RELEASE TAG OR KEYWORD.` - Specify a release tag or one of the following keywords:
- 'dev' (download the dev version from the default branch.)
- 'branch' (download the dev version from an alternative branch. Specified by appending ':branch_name')
- 'select' (a list of valid options will be offered including dev and all releases that are not draft. Latest and pre-releases will be labelled.)
- `--github-token=THE TOKEN.` - A Github Personal Access Token (Classic) that can be used to extend the GitHub API rate.

*Examples:*
- `bee download-core ../backdrop` - Download Backdrop into a 'backdrop' directory in the parent folder.

- `bee download-core --version=1.28.3` - Download the 1.28.3 release of Backdrop into the current directory.
- `bee download-core --version=dev` - Download the dev version of Backdrop into the current directory.
- `bee download-core --version=branch:1.29.x` - Download the 1.29.x branch of Backdrop into the current directory.
- `bee download-core --version=select` - Select from a list of releases of Backdrop to download into the current directory.

#### `install`
*Description:* Install Backdrop and setup a new site.
Expand Down
5 changes: 5 additions & 0 deletions includes/globals.inc
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ define('BEE_BOOTSTRAP_LANGUAGE', 7);
// 9th phase: Backdrop is fully loaded; validate and fix input data.
define('BEE_BOOTSTRAP_FULL', 8);

/**
* Define constant for path to backup directory.
*/
define('BEE_BACKUP_DIRECTORY', '../bee-backup/');

/**
* Define constants for Bee's current version and latest release.
*/
Expand Down
159 changes: 159 additions & 0 deletions includes/info.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php
/**
* @file
*
* Copies of Backdrop functions for processing info files.
*
* As download may need to operate to download Backdrop or before Backdrop is
* installed, we cannot rely on these Backdrop functions being available.
*/

/**
* @see backdrop_parse_info_format()
*/
function bee_parse_info_format($data, $process_sections = FALSE) {
$info = array();

// Separate by sections and parse individually if requested.
if ($process_sections) {
// Remove leading a trailing comments before groups.
$data = trim(preg_replace('/;.*$/m', '', $data));

// Split the content by the groups present.
$split = preg_split('/^\[(.*)\]\s*$/m', $data, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$split_count = count($split);

// If sections are found, parse each individually and add to the result.
if ($split_count > 1) {
$info_sections = array();
for ($n = 0; $n < $split_count; $n = $n + 2) {
$info_sections[$split[$n]] = bee_parse_info_format($split[$n + 1], FALSE);
}
return $info_sections;
}
}

if (preg_match_all('
@^\s* # Start at the beginning of a line, ignoring leading whitespace
((?:
[^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets,
\[[^\[\]]*\] # unless they are balanced and not nested
)+?)
\s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space)
(?:
("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes
(\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
([^\r\n]*?) # Non-quoted string
)\s*$ # Stop at the next end of a line, ignoring trailing whitespace
@msx', $data, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
// Fetch the key and value string.
$i = 0;
$parts = array();
foreach (array('key', 'value1', 'value2', 'value3') as $var) {
$parts[$var] = isset($match[++$i]) ? $match[$i] : '';
}
$value = stripslashes(substr($parts['value1'], 1, -1)) . stripslashes(substr($parts['value2'], 1, -1)) . $parts['value3'];

// Parse array syntax.
$keys = preg_split('/\]?\[/', rtrim($parts['key'], ']'));
$last = array_pop($keys);
$parent = &$info;

// Create nested arrays.
foreach ($keys as $key) {
if ($key == '') {
$key = count($parent);
}
if (!isset($parent[$key]) || !is_array($parent[$key])) {
$parent[$key] = array();
}
$parent = &$parent[$key];
}

// Handle PHP constants.
if (preg_match('/^\w+$/', $value) && defined($value)) {
$value = constant($value);
}

// Insert actual value.
if ($last == '') {
$last = count($parent);
}
$parent[$last] = $value;
}
}

return $info;
}

/**
* @see backdrop_parse_dependency
*/
function bee_parse_dependency($dependency) {
$value = array();
// Split out the optional project name.
if (strpos($dependency, ':')) {
list($project_name, $dependency) = explode(':', $dependency);
$value['project'] = $project_name;
}
// We use named subpatterns and support every op that version_compare
// supports. Also, op is optional and defaults to equals.
$p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
// Core version is always optional: 1.x-2.x and 2.x is treated the same.
$p_core = '(?:' . preg_quote(BACKDROP_CORE_COMPATIBILITY) . '-)?';
$p_major = '(?P<major>\d+)';
// By setting the minor version to x, branches can be matched.
$p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
$p_patch = '(?P<patch>(?:\d+|x)(?:-[A-Za-z]+\d+)?)?';
$parts = explode('(', $dependency, 2);
$value['name'] = trim($parts[0]);
if (isset($parts[1])) {
$value['original_version'] = '(' . $parts[1];
foreach (explode(',', $parts[1]) as $version) {
if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor\.?$p_patch/", $version, $matches)) {
$op = !empty($matches['operation']) ? $matches['operation'] : '=';
if ($matches['minor'] == 'x') {
// Backdrop considers "2.x" to mean any version that begins with
// "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
// on the other hand, treats "x" as a string; so to
// version_compare(), "2.x" is considered less than 2.0. This
// means that >=2.x and <2.x are handled by version_compare()
// as we need, but > and <= are not.
if ($op == '>' || $op == '<=') {
$matches['major']++;
}
// Equivalence can be checked by adding two restrictions.
if ($op == '=' || $op == '==') {
$value['versions'][] = array(
'op' => '<',
'version' => ($matches['major'] + 1) . '.x',
);
$op = '>=';
}
}

if (isset($matches['patch']) && ($matches['patch'] === '0' || $matches['patch'])) {
if ($matches['patch'] == 'x' && $matches['minor'] !== 'x') {
// See comments above about "x" in minor.
// Same principle applies to patch in relation to minor.
if ($op == '>' || $op == '<=') {
$matches['minor']++;
}
if ($op == '=' || $op == '==') {
$value['versions'][] = array(
'op' => '<',
'version' => $matches['major'] . '.' . ($matches['minor'] + 1) . '.x',
);
$op = '>=';
}
}
}
$version = $matches['major'] . '.' . $matches['minor'];
$version .= (isset($matches['patch']) && ($matches['patch'] === '0' || $matches['patch'])) ? '.' . $matches['patch'] : '';
$value['versions'][] = array('op' => $op, 'version' => $version);
}
}
}
return $value;
}
28 changes: 21 additions & 7 deletions tests/backdrop/DownloadCommandsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@ public function test_download_command_works() {
global $bee_test_root;
// Single module.
$output_single = shell_exec('bee download simplify');
$this->assertStringContainsString("'simplify' was downloaded into '$bee_test_root/backdrop/modules/simplify'.", (string) $output_single);
$pattern = '/\'simplify\' \([\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/backdrop\/modules\/simplify\'/';
$this->assertRegExp($pattern, $output_single);
$this->assertTrue(file_exists("$bee_test_root/backdrop/modules/simplify/simplify.info"));

// Multiple projects (theme and layout).
$output_multiple = shell_exec('bee download lumi bamboo');
$this->assertStringContainsString("'lumi' was downloaded into '$bee_test_root/backdrop/themes/lumi'.", (string) $output_multiple);
$pattern = '/\'lumi\' \([\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/backdrop\/themes\/lumi\'/';
$this->assertRegExp($pattern, $output_multiple);
$this->assertTrue(file_exists("$bee_test_root/backdrop/themes/lumi/lumi.info"));
$this->assertStringContainsString("'bamboo' was downloaded into '$bee_test_root/backdrop/layouts/bamboo'.", (string) $output_multiple);
$pattern = '/\'bamboo\' \([\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/backdrop\/layouts\/bamboo\'/';
$this->assertRegExp($pattern, $output_multiple);
$this->assertTrue(file_exists("$bee_test_root/backdrop/layouts/bamboo/bamboo.info"));

// Defined release.
$output_defined_release = shell_exec('bee download layout_custom_theme:1.x-1.0.4');
$pattern = '/\'layout_custom_theme\' \(1\.x\-1\.0\.4\, published at 2024\-02\-01T[\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/backdrop\/modules\/layout_custom_theme\'/';
// Cleanup downloads.
exec("rm -fr $bee_test_root/backdrop/modules/simplify $bee_test_root/backdrop/themes/lumi $bee_test_root/backdrop/layouts/bamboo");
exec("rm -fr $bee_test_root/backdrop/modules/simplify $bee_test_root/backdrop/themes/lumi $bee_test_root/backdrop/layouts/bamboo $bee_test_root/backdrop/modules/layout_custom_theme");
}

/**
Expand All @@ -38,16 +44,24 @@ public function test_download_core_command_works() {
global $bee_test_root;
// Download to current directory.
$output_current = shell_exec("mkdir $bee_test_root/current && cd $bee_test_root/current && bee download-core");
$this->assertStringContainsString("Backdrop was downloaded into '$bee_test_root/current'.", (string) $output_current);
$pattern = '/Backdrop \([\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/current\'/';
$this->assertRegExp($pattern, $output_current);
$this->assertTrue(file_exists("$bee_test_root/current/index.php"));

// Download to specified directory.
$output_directory = shell_exec("bee download-core $bee_test_root/directory");
$this->assertStringContainsString("Backdrop was downloaded into '$bee_test_root/directory'.", (string) $output_directory);
$pattern = '/Backdrop \([\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/directory\'/';
$this->assertRegExp($pattern, $output_directory);
$this->assertTrue(file_exists("$bee_test_root/directory/index.php"));

// Download a defined release.
$output_defined_release = shell_exec("bee download-core $bee_test_root/defined_release --version=1.30.0");
$pattern = '/Backdrop \(1\.30\.0\, published at 2025\-01\-1\dT[\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/defined_release\'/';
$this->assertRegExp($pattern, $output_defined_release);
$this->assertTrue(file_exists("$bee_test_root/defined_release/index.php"));

// Cleanup downloads.
exec("rm -fr $bee_test_root/current $bee_test_root/directory");
exec("rm -fr $bee_test_root/current $bee_test_root/directory $bee_test_root/defined_release");
}

}
12 changes: 8 additions & 4 deletions tests/multisite/MultisiteDownloadCommandsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public function test_download_command_works() {
global $bee_test_root;
// Root directory, no site specified.
$output_root = shell_exec('bee download simplify');
$this->assertStringContainsString("'simplify' was downloaded into '$bee_test_root/multisite/modules/simplify'.", (string) $output_root);
$pattern = '/\'simplify\' \([\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/multisite\/modules\/simplify\'/';
$this->assertRegExp($pattern, $output_root);
$this->assertTrue(file_exists("$bee_test_root/multisite/modules/simplify/simplify.info"));

// Root directory, site specified, 'allow-multisite-copy' option NOT
Expand All @@ -27,17 +28,20 @@ public function test_download_command_works() {

// Root directory, site specified, 'allow-multisite-copy' option included.
$output_root = shell_exec('bee --site=multi_one download --allow-multisite-copy simplify');
$this->assertStringContainsString("'simplify' was downloaded into '$bee_test_root/multisite/sites/multi_one/modules/simplify'.", (string) $output_root);
$pattern = '/\'simplify\' \([\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/multisite\/sites\/multi_one\/modules\/simplify\'/';
$this->assertRegExp($pattern, $output_root);
$this->assertTrue(file_exists("$bee_test_root/multisite/sites/multi_one/modules/simplify/simplify.info"));

// Root directory, site specified.
$output_root_site = shell_exec('bee download --site=multi_one lumi');
$this->assertStringContainsString("'lumi' was downloaded into '$bee_test_root/multisite/sites/multi_one/themes/lumi'.", (string) $output_root_site);
$pattern = '/\'lumi\' \([\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/multisite\/sites\/multi_one\/themes\/lumi\'/';
$this->assertRegExp($pattern, $output_root_site);
$this->assertTrue(file_exists("$bee_test_root/multisite/sites/multi_one/themes/lumi/lumi.info"));

// Site directory.
$output_site = shell_exec('cd sites/multi_two && bee download bamboo');
$this->assertStringContainsString("'bamboo' was downloaded into '$bee_test_root/multisite/sites/multi_two/layouts/bamboo'.", (string) $output_site);
$pattern = '/\'bamboo\' \([\w\s\.\W]*\) was downloaded into \'' . preg_quote($bee_test_root, '/') . '\/multisite\/sites\/multi_two\/layouts\/bamboo\'/';
$this->assertRegExp($pattern, $output_site);
$this->assertTrue(file_exists("$bee_test_root/multisite/sites/multi_two/layouts/bamboo/bamboo.info"));

// Cleanup downloads.
Expand Down
Loading