From 30b3644edd66a03b5a73e14692c487a8d819882c Mon Sep 17 00:00:00 2001 From: Michael Reichardt Date: Sat, 2 Mar 2024 14:37:03 +0100 Subject: [PATCH] Handle PHP version incompatibility issues, test UnzipStepRunner --- composer.json | 3 +- src/WordPress/Blueprints/BlueprintParser.php | 7 +- .../Blueprints/Model/DataClass/UnzipStep.php | 48 +++++----- .../Blueprints/Progress/ProgressEvent.php | 22 ++++- src/WordPress/Blueprints/Progress/Tracker.php | 2 +- .../Blueprints/Resources/ResourceManager.php | 11 +-- .../Runner/Step/UnzipStepRunner.php | 23 +++-- .../Zip/ZipCentralDirectoryEntry.php | 72 +++++++++++---- .../Zip/ZipEndCentralDirectoryEntry.php | 31 +++++-- src/WordPress/Zip/ZipFileEntry.php | 51 +++++++--- src/WordPress/Zip/ZipStreamReader.php | 2 +- .../Runner/Step/UnzipStepRunnerTest.php | 87 ++++++++++++++++++ .../{Unit => Blueprints/Runner/Step}/test.zip | Bin tests/Unit/UnzipStep.php | 23 ----- 14 files changed, 272 insertions(+), 110 deletions(-) create mode 100644 tests/Blueprints/Runner/Step/UnzipStepRunnerTest.php rename tests/{Unit => Blueprints/Runner/Step}/test.zip (100%) delete mode 100644 tests/Unit/UnzipStep.php diff --git a/composer.json b/composer.json index 5ae966c2..335c8bf5 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "symfony/http-kernel": "*", "pimple/pimple": "*", "psr/simple-cache": "*", - "opis/json-schema": "*" + "opis/json-schema": "*", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "*", diff --git a/src/WordPress/Blueprints/BlueprintParser.php b/src/WordPress/Blueprints/BlueprintParser.php index 76b98330..d102ccb4 100644 --- a/src/WordPress/Blueprints/BlueprintParser.php +++ b/src/WordPress/Blueprints/BlueprintParser.php @@ -36,10 +36,9 @@ public function parse($rawBlueprint) 'Unsupported $rawBlueprint type. Use a JSON string, a parsed JSON object, or a BlueprintBuilder instance.' ); } - // TODO Evaluate waring: missing function's return type - public function fromJson($json) - { - // TODO Evaluate warning: 'ext-json' is missing in composer.json + + public function fromJson($json): ?Blueprint + { return $this->fromObject(json_decode($json, false)); } diff --git a/src/WordPress/Blueprints/Model/DataClass/UnzipStep.php b/src/WordPress/Blueprints/Model/DataClass/UnzipStep.php index 7f292e18..32bcc9bd 100644 --- a/src/WordPress/Blueprints/Model/DataClass/UnzipStep.php +++ b/src/WordPress/Blueprints/Model/DataClass/UnzipStep.php @@ -2,60 +2,64 @@ namespace WordPress\Blueprints\Model\DataClass; -class UnzipStep implements StepDefinitionInterface -{ +class UnzipStep implements StepDefinitionInterface { + public const DISCRIMINATOR = 'unzip'; /** @var Progress */ - public $progress; + public Progress $progress; /** @var bool */ - public $continueOnError; + public bool $continue_on_error; - /** @var string */ - public $step = 'unzip'; + /** + * Step + * + * @var string + */ + public string $step = 'unzip'; - /** @var string|ResourceDefinitionInterface */ - public $zipFile; + /** + * The file to extract + * + * @var string|ResourceDefinitionInterface + */ + public $zip_file; /** * The path to extract the zip file to + * * @var string */ - public $extractToPath; + public string $extract_to_path; - public function setProgress(Progress $progress) - { + public function setProgress( Progress $progress ) { $this->progress = $progress; return $this; } - public function setContinueOnError(bool $continueOnError) - { - $this->continueOnError = $continueOnError; + public function setContinueOnError( bool $continue_on_error ) { + $this->continue_on_error = $continue_on_error; return $this; } - public function setStep(string $step) - { + public function setStep( string $step ) { $this->step = $step; return $this; } - public function setZipFile($zipFile) - { - $this->zipFile = $zipFile; + public function setZipFile( $zip_file ) { + $this->zip_file = $zip_file; return $this; } - public function setExtractToPath(string $extractToPath) - { - $this->extractToPath = $extractToPath; + public function setExtractToPath( string $extract_to_path ) { + $this->extract_to_path = $extract_to_path; return $this; } } diff --git a/src/WordPress/Blueprints/Progress/ProgressEvent.php b/src/WordPress/Blueprints/Progress/ProgressEvent.php index ba4c449f..11355033 100644 --- a/src/WordPress/Blueprints/Progress/ProgressEvent.php +++ b/src/WordPress/Blueprints/Progress/ProgressEvent.php @@ -8,11 +8,25 @@ * Custom event providing progress details. */ class ProgressEvent extends Event { + /** + * The progress percentage as a number between 0 and 100. + * + * @var float $progress + */ + public float $progress; + + /** + * The caption to display during progress, a string. + * + * @var ?string $caption + */ + public ?string $caption; + public function __construct( - /** The progress percentage as a number between 0 and 100. */ - public float $progress, - /** The caption to display during progress, a string. */ - public ?string $caption, + float $progress, + ?string $caption ) { + $this->caption = $caption; + $this->progress = $progress; } } diff --git a/src/WordPress/Blueprints/Progress/Tracker.php b/src/WordPress/Blueprints/Progress/Tracker.php index b68d6b3d..ed23b027 100644 --- a/src/WordPress/Blueprints/Progress/Tracker.php +++ b/src/WordPress/Blueprints/Progress/Tracker.php @@ -46,7 +46,7 @@ class Tracker { private $weight; private $subTrackers = []; - public readonly EventDispatcher $events; + public EventDispatcher $events; public function __construct( $options = [] ) { $this->weight = $options['weight'] ?? 1; diff --git a/src/WordPress/Blueprints/Resources/ResourceManager.php b/src/WordPress/Blueprints/Resources/ResourceManager.php index 5f9966bb..1a4a80ff 100644 --- a/src/WordPress/Blueprints/Resources/ResourceManager.php +++ b/src/WordPress/Blueprints/Resources/ResourceManager.php @@ -4,18 +4,19 @@ use Symfony\Component\Filesystem\Filesystem; use WordPress\Blueprints\Compile\CompiledResource; -use WordPress\Blueprints\Model\DataClass\ResourceDefinitionInterface; -use WordPress\Blueprints\Resource\Resolver\ResourceResolverInterface; +use WordPress\Blueprints\Resources\Resolver\ResourceResolverInterface; class ResourceManager { protected Filesystem $fs; protected ResourceMap $map; + protected ResourceResolverInterface $resourceResolver; public function __construct( - protected ResourceResolverInterface $resourceResolver + ResourceResolverInterface $resourceResolver ) { - $this->fs = new Filesystem(); + $this->resourceResolver = $resourceResolver; + $this->fs = new Filesystem(); $this->map = new ResourceMap(); } @@ -33,7 +34,6 @@ public function getStream( $key ) { return $this->map[ $key ]; } - public function bufferToTemporaryFile( $resource, $callback, $suffix = null ) { $fp = $this->getStream( $resource ); $path = $this->fs->tempnam( sys_get_temp_dir(), 'resource', $suffix ); @@ -45,5 +45,4 @@ public function bufferToTemporaryFile( $resource, $callback, $suffix = null ) { $this->fs->remove( $path ); } } - } diff --git a/src/WordPress/Blueprints/Runner/Step/UnzipStepRunner.php b/src/WordPress/Blueprints/Runner/Step/UnzipStepRunner.php index 22d081b3..54597080 100644 --- a/src/WordPress/Blueprints/Runner/Step/UnzipStepRunner.php +++ b/src/WordPress/Blueprints/Runner/Step/UnzipStepRunner.php @@ -2,21 +2,32 @@ namespace WordPress\Blueprints\Runner\Step; +use WordPress\Blueprints\BlueprintException; use WordPress\Blueprints\Model\DataClass\UnzipStep; use WordPress\Blueprints\Progress\Tracker; use function WordPress\Zip\zip_extract_to; class UnzipStepRunner extends BaseStepRunner { + /** + * Runs the Unzip Step + * + * @param UnzipStep $input Step. + * @param Tracker $tracker Tracker. + * @return void + * @throws BlueprintException + */ public function run( UnzipStep $input, - Tracker $progress = null + Tracker $tracker ) { - $progress?->set( 10, 'Unzipping...' ); + $tracker->set( 10, 'Unzipping...' ); - // @TODO: Expose a generic helper method for this, e.g. $this->getExecutionContext()->resolvePath($input->extractToPath); - $toPath = $this->getRuntime()->getDocumentRoot() . '/' . $input->extractToPath; - zip_extract_to( $this->getResource( $input->zipFile ), $toPath ); + $resolved_to_path = $this->getRuntime()->resolvePath( $input->extract_to_path ); + try { + zip_extract_to( $this->getResource( $input->zip_file ), $resolved_to_path ); + } catch ( \Throwable $exception ) { + throw new BlueprintException( "Failed to unzip file \"$input->zip_file\".", 0, $exception ); + } } - } diff --git a/src/WordPress/Zip/ZipCentralDirectoryEntry.php b/src/WordPress/Zip/ZipCentralDirectoryEntry.php index 29f87d2d..0527dd7e 100644 --- a/src/WordPress/Zip/ZipCentralDirectoryEntry.php +++ b/src/WordPress/Zip/ZipCentralDirectoryEntry.php @@ -4,28 +4,62 @@ class ZipCentralDirectoryEntry { - public readonly bool $isDirectory; + public bool $isDirectory; + public int $firstByteAt; + public int $versionCreated; + public int $versionNeeded; + public int $generalPurpose; + public int $compressionMethod; + public int $lastModifiedTime; + public int $lastModifiedDate; + public int $crc; + public int $compressedSize; + public int $uncompressedSize; + public int $diskNumber; + public int $internalAttributes; + public int $externalAttributes; + public int $lastByteAt; + public string $path; + public string $extra; + public string $fileComment; public function __construct( - public readonly int $versionCreated, - public readonly int $versionNeeded, - public readonly int $generalPurpose, - public readonly int $compressionMethod, - public readonly int $lastModifiedTime, - public readonly int $lastModifiedDate, - public readonly int $crc, - public readonly int $compressedSize, - public readonly int $uncompressedSize, - public readonly int $diskNumber, - public readonly int $internalAttributes, - public readonly int $externalAttributes, - public readonly int $firstByteAt, - public readonly int $lastByteAt, - public readonly string $path, - public readonly string $extra, - public readonly string $fileComment, + int $versionCreated, + int $versionNeeded, + int $generalPurpose, + int $compressionMethod, + int $lastModifiedTime, + int $lastModifiedDate, + int $crc, + int $compressedSize, + int $uncompressedSize, + int $diskNumber, + int $internalAttributes, + int $externalAttributes, + int $firstByteAt, + int $lastByteAt, + string $path, + string $extra, + string $fileComment ) { - $this->isDirectory = $this->path[ - 1 ] === '/'; + $this->fileComment = $fileComment; + $this->extra = $extra; + $this->path = $path; + $this->lastByteAt = $lastByteAt; + $this->externalAttributes = $externalAttributes; + $this->internalAttributes = $internalAttributes; + $this->diskNumber = $diskNumber; + $this->uncompressedSize = $uncompressedSize; + $this->compressedSize = $compressedSize; + $this->crc = $crc; + $this->lastModifiedDate = $lastModifiedDate; + $this->lastModifiedTime = $lastModifiedTime; + $this->compressionMethod = $compressionMethod; + $this->generalPurpose = $generalPurpose; + $this->versionNeeded = $versionNeeded; + $this->versionCreated = $versionCreated; + $this->firstByteAt = $firstByteAt; + $this->isDirectory = $this->path[- 1] === '/'; } public function isFileEntry() { diff --git a/src/WordPress/Zip/ZipEndCentralDirectoryEntry.php b/src/WordPress/Zip/ZipEndCentralDirectoryEntry.php index e6d70228..ed1df1df 100644 --- a/src/WordPress/Zip/ZipEndCentralDirectoryEntry.php +++ b/src/WordPress/Zip/ZipEndCentralDirectoryEntry.php @@ -4,17 +4,32 @@ class ZipEndCentralDirectoryEntry { + public int $diskNumber; + public int $centralDirectoryStartDisk; + public int $numberCentralDirectoryRecordsOnThisDisk; + public int $numberCentralDirectoryRecords; + public int $centralDirectorySize; + public int $centralDirectoryOffset; + public string $comment; + public function __construct( - public readonly int $diskNumber, - public readonly int $centralDirectoryStartDisk, - public readonly int $numberCentralDirectoryRecordsOnThisDisk, - public readonly int $numberCentralDirectoryRecords, - public readonly int $centralDirectorySize, - public readonly int $centralDirectoryOffset, - public readonly string $comment + int $diskNumber, + int $centralDirectoryStartDisk, + int $numberCentralDirectoryRecordsOnThisDisk, + int $numberCentralDirectoryRecords, + int $centralDirectorySize, + int $centralDirectoryOffset, + string $comment ) { + $this->comment = $comment; + $this->centralDirectoryOffset = $centralDirectoryOffset; + $this->centralDirectorySize = $centralDirectorySize; + $this->numberCentralDirectoryRecords = $numberCentralDirectoryRecords; + $this->numberCentralDirectoryRecordsOnThisDisk = $numberCentralDirectoryRecordsOnThisDisk; + $this->centralDirectoryStartDisk = $centralDirectoryStartDisk; + $this->diskNumber = $diskNumber; } - + public function isFileEntry() { return false; } diff --git a/src/WordPress/Zip/ZipFileEntry.php b/src/WordPress/Zip/ZipFileEntry.php index 44e1cbbc..3b794525 100644 --- a/src/WordPress/Zip/ZipFileEntry.php +++ b/src/WordPress/Zip/ZipFileEntry.php @@ -3,26 +3,47 @@ namespace WordPress\Zip; class ZipFileEntry { - public readonly bool $isDirectory; + public bool $isDirectory; + public int $version; + public int $generalPurpose; + public int $compressionMethod; + public int $lastModifiedTime; + public int $lastModifiedDate; + public int $crc; + public int $compressedSize; + public int $uncompressedSize; + public string $path; + public string $extra; + public string $bytes; public function __construct( - public readonly int $version, - public readonly int $generalPurpose, - public readonly int $compressionMethod, - public readonly int $lastModifiedTime, - public readonly int $lastModifiedDate, - public readonly int $crc, - public readonly int $compressedSize, - public readonly int $uncompressedSize, - public readonly string $path, - public readonly string $extra, - public readonly string $bytes + int $version, + int $generalPurpose, + int $compressionMethod, + int $lastModifiedTime, + int $lastModifiedDate, + int $crc, + int $compressedSize, + int $uncompressedSize, + string $path, + string $extra, + string $bytes ) { - $this->isDirectory = $this->path[ - 1 ] === '/'; + $this->bytes = $bytes; + $this->extra = $extra; + $this->path = $path; + $this->uncompressedSize = $uncompressedSize; + $this->compressedSize = $compressedSize; + $this->crc = $crc; + $this->lastModifiedDate = $lastModifiedDate; + $this->lastModifiedTime = $lastModifiedTime; + $this->compressionMethod = $compressionMethod; + $this->generalPurpose = $generalPurpose; + $this->version = $version; + $this->isDirectory = $this->path[- 1] === '/'; } - + public function isFileEntry() { return true; } - } diff --git a/src/WordPress/Zip/ZipStreamReader.php b/src/WordPress/Zip/ZipStreamReader.php index 409e31f5..6d32671c 100644 --- a/src/WordPress/Zip/ZipStreamReader.php +++ b/src/WordPress/Zip/ZipStreamReader.php @@ -192,7 +192,7 @@ static protected function readEndCentralDirectoryEntry( $stream ): ZipEndCentral * * @return false|string */ - static protected function read_bytes( $stream, $length ): string|bool { + static protected function read_bytes( $stream, $length ) { if ( $length === 0 ) { return ''; } diff --git a/tests/Blueprints/Runner/Step/UnzipStepRunnerTest.php b/tests/Blueprints/Runner/Step/UnzipStepRunnerTest.php new file mode 100644 index 00000000..e101fc1a --- /dev/null +++ b/tests/Blueprints/Runner/Step/UnzipStepRunnerTest.php @@ -0,0 +1,87 @@ +document_root = Path::makeAbsolute( 'test', sys_get_temp_dir() ); + $this->runtime = new Runtime( $this->document_root ); + + $resource_manager = $this->createStub( ResourceManager::class ); + $resource_manager->method( 'getStream' ) + ->willReturn( fopen( __DIR__ . '/test.zip', 'rb' ) ); + + $this->step = new UnzipStepRunner(); + $this->step->setRuntime( $this->runtime ); + $this->step->setResourceManager( $resource_manager ); + + $this->file_system = new Filesystem(); + } + + /** + * @after + */ + public function after() { + $this->file_system->remove( $this->document_root ); + } + + public function testUnzipFileWhenUsingAbsolutePath(): void { + $input = new UnzipStep(); + $zip = __DIR__ . '/test.zip'; + $input->setZipFile( $zip ); + $relative_path = 'dir'; + $absolute_path = $this->runtime->resolvePath( $relative_path ); + $input->setExtractToPath( $absolute_path ); + + $this->step->run( $input, new Tracker() ); + + $this->assertDirectoryExists( $absolute_path ); + } + + public function testUnzipFileWhenUsingRelativePath(): void { + $input = new UnzipStep(); + $zip = __DIR__ . '/test.zip'; + $input->setZipFile( $zip ); + $relative_path = 'dir'; + $input->setExtractToPath( $relative_path ); + + $this->step->run( $input, new Tracker() ); + + $absolute_path = $this->runtime->resolvePath( $relative_path ); + $this->assertDirectoryExists( $absolute_path ); + } +} diff --git a/tests/Unit/test.zip b/tests/Blueprints/Runner/Step/test.zip similarity index 100% rename from tests/Unit/test.zip rename to tests/Blueprints/Runner/Step/test.zip diff --git a/tests/Unit/UnzipStep.php b/tests/Unit/UnzipStep.php deleted file mode 100644 index da49c96a..00000000 --- a/tests/Unit/UnzipStep.php +++ /dev/null @@ -1,23 +0,0 @@ -execute(); - - expect( file_exists( __DIR__ . "/test/TEST" ) )->toBeTrue(); - - fclose( $fp ); - } finally { - if ( file_exists( __DIR__ . "/test/TEST" ) ) { - unlink( __DIR__ . "/test/TEST" ); - } - if ( file_exists( __DIR__ . "/test" ) ) { - rmdir( __DIR__ . "/test" ); - } - } -} );