Initial Drupal 11 with DDEV setup

This commit is contained in:
gluebox
2025-10-08 11:39:17 -04:00
commit 89ef74b305
25344 changed files with 2599172 additions and 0 deletions

View File

@ -0,0 +1,295 @@
<?php
namespace Robo\Task\Archive;
use Robo\Result;
use Robo\Task\BaseTask;
use Robo\Task\Filesystem\Tasks as FilesystemTaskLoader;
use Robo\Contract\BuilderAwareInterface;
use Robo\TaskAccessor;
/**
* Extracts an archive.
*
* Note that often, distributions are packaged in tar or zip archives
* where the topmost folder may contain variable information, such as
* the release date, or the version of the package. This information
* is very useful when unpacking by hand, but arbitrarily-named directories
* are much less useful to scripts. Therefore, by default, Extract will
* remove the top-level directory, and instead store all extracted files
* into the directory specified by $archivePath.
*
* To keep the top-level directory when extracting, use
* `preserveTopDirectory(true)`.
*
* ``` php
* <?php
* $this->taskExtract($archivePath)
* ->to($destination)
* ->preserveTopDirectory(false) // the default
* ->run();
* ?>
* ```
*/
class Extract extends BaseTask implements BuilderAwareInterface
{
use TaskAccessor;
use FilesystemTaskLoader;
/**
* @var string
*/
protected $filename;
/**
* @var string
*/
protected $to;
/**
* @var bool
*/
private $preserveTopDirectory = false;
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* Location to store extracted files.
*
* @param string $to
*
* @return $this
*/
public function to($to)
{
$this->to = $to;
return $this;
}
/**
* @param bool $preserve
*
* @return $this
*/
public function preserveTopDirectory($preserve = true)
{
$this->preserveTopDirectory = $preserve;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!file_exists($this->filename)) {
$this->printTaskError("File {filename} does not exist", ['filename' => $this->filename]);
return false;
}
if (!($mimetype = static::archiveType($this->filename))) {
$this->printTaskError("Could not determine type of archive for {filename}", ['filename' => $this->filename]);
return false;
}
$umask = 0777 - umask();
// We will first extract to $extractLocation and then move to $this->to
$extractLocation = $this->getTempDir();
@mkdir($extractLocation, $umask, true);
$destinationParentDir = dirname($this->to);
if (!file_exists($destinationParentDir)) {
@mkdir($destinationParentDir, $umask, true);
}
$this->startTimer();
$this->printTaskInfo("Extracting {filename}", ['filename' => $this->filename]);
$result = $this->extractAppropriateType($mimetype, $extractLocation);
if ($result->wasSuccessful()) {
$this->printTaskInfo("{filename} extracted", ['filename' => $this->filename]);
// Now, we want to move the extracted files to $this->to. There
// are two possibilities that we must consider:
//
// (1) Archived files were encapsulated in a folder with an arbitrary name
// (2) There was no encapsulating folder, and all the files in the archive
// were extracted into $extractLocation
//
// In the case of (1), we want to move and rename the encapsulating folder
// to $this->to.
//
// In the case of (2), we will just move and rename $extractLocation.
$filesInExtractLocation = glob("$extractLocation/*");
$hasEncapsulatingFolder = ((count($filesInExtractLocation) == 1) && is_dir($filesInExtractLocation[0]));
if ($hasEncapsulatingFolder && !$this->preserveTopDirectory) {
$this
->taskFilesystemStack()
->rename($filesInExtractLocation[0], $this->to)
->remove($extractLocation)
->run();
} else {
$this
->taskFilesystemStack()
->rename($extractLocation, $this->to)
->run();
}
}
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* @param string $mimetype
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractAppropriateType($mimetype, $extractLocation)
{
// Perform the extraction of a zip file.
if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) {
return $this->extractZip($extractLocation);
}
return $this->extractTar($extractLocation);
}
/**
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractZip($extractLocation)
{
if (!extension_loaded('zlib')) {
return Result::errorMissingExtension($this, 'zlib', 'zip extracting');
}
$zip = new \ZipArchive();
if (($status = $zip->open($this->filename)) !== true) {
return Result::error($this, "Could not open zip archive {$this->filename}");
}
if (!$zip->extractTo($extractLocation)) {
return Result::error($this, "Could not extract zip archive {$this->filename}");
}
$zip->close();
return Result::success($this);
}
/**
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractTar($extractLocation)
{
if (!class_exists('Archive_Tar')) {
return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
}
$tar_object = new \Archive_Tar($this->filename);
if (!$tar_object->extract($extractLocation)) {
return Result::error($this, "Could not extract tar archive {$this->filename}");
}
return Result::success($this);
}
/**
* @param string $filename
*
* @return bool|string
*/
protected static function archiveType($filename)
{
$content_type = false;
if (class_exists('finfo')) {
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$content_type = $finfo->file($filename);
// If finfo cannot determine the content type, then we will try other methods
if ($content_type == 'application/octet-stream') {
$content_type = false;
}
}
// Examing the file's magic header bytes.
if (!$content_type) {
if ($file = fopen($filename, 'rb')) {
$first = fread($file, 2);
fclose($file);
if ($first !== false) {
// Interpret the two bytes as a little endian 16-bit unsigned int.
$data = unpack('v', $first);
switch ($data[1]) {
case 0x8b1f:
// First two bytes of gzip files are 0x1f, 0x8b (little-endian).
// See https://www.gzip.org/zlib/rfc-gzip.html#header-trailer
$content_type = 'application/x-gzip';
break;
case 0x4b50:
// First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian).
// See https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
$content_type = 'application/zip';
break;
case 0x5a42:
// First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian).
// See https://en.wikipedia.org/wiki/Bzip2#File_format
$content_type = 'application/x-bzip2';
break;
}
}
}
}
// 3. Lastly if above methods didn't work, try to guess the mime type from
// the file extension. This is useful if the file has no identificable magic
// header bytes (for example tarballs).
if (!$content_type) {
// Remove querystring from the filename, if present.
$filename = basename(current(explode('?', $filename, 2)));
$extension_mimetype = array(
'.tar.gz' => 'application/x-gzip',
'.tgz' => 'application/x-gzip',
'.tar' => 'application/x-tar',
);
foreach ($extension_mimetype as $extension => $ct) {
if (substr($filename, -strlen($extension)) === $extension) {
$content_type = $ct;
break;
}
}
}
return $content_type;
}
/**
* @return string
*/
protected function getTempDir()
{
return $this->to . '-tmp' . rand() . time();
}
/**
* @deprecated Use $this->getTempDir() instead.
*
* @return string
*
* @see getTempDir
*/
protected static function getTmpDir()
{
return getcwd() . '/tmp' . rand() . time();
}
}

View File

@ -0,0 +1,291 @@
<?php
namespace Robo\Task\Archive;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Task\BaseTask;
use Symfony\Component\Finder\Finder;
/**
* Creates a zip or tar archive.
*
* ``` php
* <?php
* $this->taskPack(
* <archiveFile>)
* ->add('README') // Puts file 'README' in archive at the root
* ->add('project') // Puts entire contents of directory 'project' in archinve inside 'project'
* ->addFile('dir/file.txt', 'file.txt') // Takes 'file.txt' from cwd and puts it in archive inside 'dir'.
* ->exclude(['dir\/.*.zip', '.*.md']) // Add regex (or array of regex) to the excluded patterns list.
* ->run();
* ?>
* ```
*/
class Pack extends BaseTask implements PrintedInterface
{
/**
* The list of items to be packed into the archive.
*
* @var array
*/
private $items = [];
/**
* The full path to the archive to be created.
*
* @var string
*/
private $archiveFile;
/**
* A list of regex patterns to exclude from the archive.
*
* @var array
*/
private $ignoreList;
/**
* Construct the class.
*
* @param string $archiveFile
* The full path and name of the archive file to create.
*
* @since 1.0
*/
public function __construct($archiveFile)
{
$this->archiveFile = $archiveFile;
$this->ignoreList = [];
}
/**
* Satisfy the parent requirement.
*
* @return bool
* Always returns true.
*
* @since 1.0
*/
public function getPrinted()
{
return true;
}
/**
* @param string $archiveFile
*
* @return $this
*/
public function archiveFile($archiveFile)
{
$this->archiveFile = $archiveFile;
return $this;
}
/**
* Add an item to the archive. Like file_exists(), the parameter
* may be a file or a directory.
*
* @param string $placementLocation
* Relative path and name of item to store in archive.
* @param string $filesystemLocation
* Absolute or relative path to file or directory's location in filesystem.
*
* @return $this
*/
public function addFile($placementLocation, $filesystemLocation)
{
$this->items[$placementLocation] = $filesystemLocation;
return $this;
}
/**
* Alias for addFile, in case anyone has angst about using
* addFile with a directory.
*
* @param string $placementLocation
* Relative path and name of directory to store in archive.
* @param string $filesystemLocation
* Absolute or relative path to directory or directory's location in filesystem.
*
* @return $this
*/
public function addDir($placementLocation, $filesystemLocation)
{
$this->addFile($placementLocation, $filesystemLocation);
return $this;
}
/**
* Add a file or directory, or list of same to the archive.
*
* @param string|array $item
* If given a string, should contain the relative filesystem path to the
* the item to store in archive; this will also be used as the item's
* path in the archive, so absolute paths should not be used here.
* If given an array, the key of each item should be the path to store
* in the archive, and the value should be the filesystem path to the
* item to store.
*
* @return $this
*/
public function add($item)
{
if (is_array($item)) {
$this->items = array_merge($this->items, $item);
} else {
$this->addFile($item, $item);
}
return $this;
}
/**
* Allow files or folder to be excluded from the archive. Use regex, without enclosing slashes.
*
* @param string|string[]
* A regex (or array of) to be excluded.
*
* @return $this
*/
public function exclude($ignoreList)
{
$this->ignoreList = array_merge($this->ignoreList, (array) $ignoreList);
return $this;
}
/**
* Create a zip archive for distribution.
*
* @return \Robo\Result
*
* @since 1.0
*/
public function run()
{
$this->startTimer();
// Use the file extension to determine what kind of archive to create.
$fileInfo = new \SplFileInfo($this->archiveFile);
$extension = strtolower($fileInfo->getExtension());
if (empty($extension)) {
return Result::error($this, "Archive filename must use an extension (e.g. '.zip') to specify the kind of archive to create.");
}
try {
// Inform the user which archive we are creating
$this->printTaskInfo("Creating archive {filename}", ['filename' => $this->archiveFile]);
if ($extension == 'zip') {
$result = $this->archiveZip($this->archiveFile, $this->items);
} else {
$result = $this->archiveTar($this->archiveFile, $this->items);
}
$this->printTaskSuccess("{filename} created.", ['filename' => $this->archiveFile]);
} catch (\Exception $e) {
$this->printTaskError("Could not create {filename}. {exception}", ['filename' => $this->archiveFile, 'exception' => $e->getMessage(), '_style' => ['exception' => '']]);
$result = Result::error($this, sprintf('Could not create %s. %s', $this->archiveFile, $e->getMessage()));
}
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* @param string $archiveFile
* @param array $items
*
* @return \Robo\Result
*/
protected function archiveTar($archiveFile, $items)
{
if (!class_exists('Archive_Tar')) {
return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
}
$tar_object = new \Archive_Tar($archiveFile);
if (!empty($this->ignoreList)) {
$regexp = '#/' . join('$|/', $this->ignoreList) . '#';
$tar_object->setIgnoreRegexp($regexp);
}
foreach ($items as $placementLocation => $filesystemLocation) {
$p_remove_dir = $filesystemLocation;
$p_add_dir = $placementLocation;
if (is_file($filesystemLocation)) {
$p_remove_dir = dirname($filesystemLocation);
$p_add_dir = dirname($placementLocation);
if (basename($filesystemLocation) != basename($placementLocation)) {
return Result::error($this, "Tar archiver does not support renaming files during extraction; could not add $filesystemLocation as $placementLocation.");
}
}
if (!$tar_object->addModify([$filesystemLocation], $p_add_dir, $p_remove_dir)) {
return Result::error($this, "Could not add $filesystemLocation to the archive.");
}
}
return Result::success($this);
}
/**
* @param string $archiveFile
* @param array $items
*
* @return \Robo\Result
*/
protected function archiveZip($archiveFile, $items)
{
if (!extension_loaded('zlib') || !class_exists(\ZipArchive::class)) {
return Result::errorMissingExtension($this, 'zlib', 'zip packing');
}
$zip = new \ZipArchive();
if (!$zip->open($archiveFile, \ZipArchive::CREATE)) {
return Result::error($this, "Could not create zip archive {$archiveFile}");
}
$result = $this->addItemsToZip($zip, $items);
$zip->close();
return $result;
}
/**
* @param \ZipArchive $zip
* @param array $items
*
* @return \Robo\Result
*/
protected function addItemsToZip($zip, $items)
{
foreach ($items as $placementLocation => $filesystemLocation) {
if (is_dir($filesystemLocation)) {
$finder = new Finder();
$finder->files()->in($filesystemLocation)->ignoreDotFiles(false);
if (!empty($this->ignoreList)) {
// Add slashes so Symfony Finder patterns work like Archive_Tar ones.
$zipIgnoreList = preg_filter('/^|$/', '/', $this->ignoreList);
$finder->notName($zipIgnoreList)->notPath($zipIgnoreList);
}
foreach ($finder as $file) {
// Replace Windows slashes or resulting zip will have issues on *nixes.
$relativePathname = str_replace('\\', '/', $file->getRelativePathname());
if (!$zip->addFile($file->getRealpath(), "{$placementLocation}/{$relativePathname}")) {
return Result::error($this, "Could not add directory $filesystemLocation to the archive; error adding {$file->getRealpath()}.");
}
}
} elseif (is_file($filesystemLocation)) {
if (!$zip->addFile($filesystemLocation, $placementLocation)) {
return Result::error($this, "Could not add file $filesystemLocation to the archive.");
}
} else {
return Result::error($this, "Could not find $filesystemLocation for the archive.");
}
}
return Result::success($this);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Robo\Task\Archive;
trait Tasks
{
/**
* @param string $filename
*
* @return \Robo\Task\Archive\Pack|\Robo\Collection\CollectionBuilder
*/
protected function taskPack($filename)
{
return $this->task(Pack::class, $filename);
}
/**
* @param string $filename
*
* @return \Robo\Task\Archive\Extract|\Robo\Collection\CollectionBuilder
*/
protected function taskExtract($filename)
{
return $this->task(Extract::class, $filename);
}
}