From 35fd723748f7bd6cd4dd4ed06639f52316051c54 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Thu, 4 Aug 2022 17:36:19 +1200 Subject: [PATCH 01/32] DEP Update core dependencies for CMS 5 --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 113f61c1..7318a1a9 100644 --- a/composer.json +++ b/composer.json @@ -19,16 +19,16 @@ } ], "require": { - "php": "^7.4 || ^8.0", - "silverstripe/framework": "^4.10", - "silverstripe/vendor-plugin": "^1.0", - "symfony/filesystem": "^3.4|^4.0|^5.0", + "php": "^8.1", + "silverstripe/framework": "^5", + "silverstripe/vendor-plugin": "^2", + "symfony/filesystem": "^5.0", "intervention/image": "^2.7", "league/flysystem": "^1.0.70" }, "require-dev": { - "silverstripe/recipe-testing": "^2", - "silverstripe/versioned": "^1@dev", + "silverstripe/recipe-testing": "^3", + "silverstripe/versioned": "^2", "squizlabs/php_codesniffer": "^3", "mikey179/vfsstream": "^1.6" }, From 22ab6c3101f212df9cc4d5a736e1b7f44c3cea2c Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Tue, 9 Aug 2022 17:12:18 +1200 Subject: [PATCH 02/32] DEP Update dependencies for CMS 5 --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 7318a1a9..2a609143 100644 --- a/composer.json +++ b/composer.json @@ -22,15 +22,15 @@ "php": "^8.1", "silverstripe/framework": "^5", "silverstripe/vendor-plugin": "^2", - "symfony/filesystem": "^5.0", - "intervention/image": "^2.7", - "league/flysystem": "^1.0.70" + "symfony/filesystem": "^v5.4.11", + "intervention/image": "^2.7.2", + "league/flysystem": "^1.1.9" }, "require-dev": { "silverstripe/recipe-testing": "^3", "silverstripe/versioned": "^2", - "squizlabs/php_codesniffer": "^3", - "mikey179/vfsstream": "^1.6" + "squizlabs/php_codesniffer": "^3.7", + "mikey179/vfsstream": "^v1.6.11" }, "suggest": { "ext-exif": "If you use GD backend (the default) you may want to have EXIF extension installed to elude some tricky issues" From 32dd083867d71ad26494f61fe15c607c8c17cc5d Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Thu, 1 Sep 2022 17:03:24 +1200 Subject: [PATCH 03/32] DEP Require symfony ^6.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2a609143..f72566d3 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "php": "^8.1", "silverstripe/framework": "^5", "silverstripe/vendor-plugin": "^2", - "symfony/filesystem": "^v5.4.11", + "symfony/filesystem": "^6.1", "intervention/image": "^2.7.2", "league/flysystem": "^1.1.9" }, From fc5722e6d83c517b8c7698367089fae0a5344563 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> Date: Thu, 29 Sep 2022 18:12:51 +1300 Subject: [PATCH 04/32] MNT Fix variant for image manipulation test (#518) --- tests/php/ImageManipulationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/php/ImageManipulationTest.php b/tests/php/ImageManipulationTest.php index f17a18d5..8a769e41 100644 --- a/tests/php/ImageManipulationTest.php +++ b/tests/php/ImageManipulationTest.php @@ -367,7 +367,7 @@ public function renderProvider() { $alt = 'This is a image Title'; $src = '/assets/ImageTest/folder/test-image.png'; - $srcCroped = '/assets/ImageTest/folder/test-image__CropWidthWyIxMDAiXQ.png'; + $srcCroped = '/assets/ImageTest/folder/test-image__CropWidthWzEwMF0.png'; return [ 'Simple output' => [ From a730c06283e19d5e241adb2a774c08dae48adee9 Mon Sep 17 00:00:00 2001 From: Sabina Talipova <87288324+sabina-talipova@users.noreply.github.com> Date: Tue, 4 Oct 2022 10:49:51 +1300 Subject: [PATCH 05/32] ENH Check canViewFile permissions before automatically grant Session access to the file (#517) --- src/File.php | 12 +++- tests/php/FileTest.php | 60 +++++++++++++++++++ .../Shortcodes/ImageShortcodeProviderTest.php | 9 +++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/File.php b/src/File.php index d41bfa57..27c6721d 100644 --- a/src/File.php +++ b/src/File.php @@ -891,8 +891,12 @@ public function getAbsoluteURL() * @param bool $grant Ensures that the url for any protected assets is granted for the current user. * @return string */ - public function getURL($grant = true) + public function getURL($grant = false) { + if (!$grant && $this->canView()) { + $grant = true; + } + if ($this->File->exists()) { return $this->File->getURL($grant); } @@ -905,8 +909,12 @@ public function getURL($grant = true) * @param bool $grant Ensures that the url for any protected assets is granted for the current user. * @return string */ - public function getSourceURL($grant = true) + public function getSourceURL($grant = false) { + if (!$grant && $this->canView()) { + $grant = true; + } + if ($this->File->exists()) { return $this->File->getSourceURL($grant); } diff --git a/tests/php/FileTest.php b/tests/php/FileTest.php index 294635a2..91ba0a04 100644 --- a/tests/php/FileTest.php +++ b/tests/php/FileTest.php @@ -14,6 +14,7 @@ use SilverStripe\Assets\Storage\AssetStore; use SilverStripe\Assets\Tests\FileTest\MyCustomFile; use SilverStripe\Control\Director; +use SilverStripe\Control\Controller; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\SapphireTest; @@ -370,8 +371,67 @@ public function testGetURL() // because ProtectedAdapter doesn't know about custom base dirs in TestAssetStore $this->assertEquals('/assets/55b443b601/FileTest.txt', $rootfile->getURL()); + // Login as ADMIN and grant session access by default + $session = Controller::curr()->getRequest()->getSession(); + $granted = $session->get(FlysystemAssetStore::GRANTS_SESSION); + + $this->assertEquals(['55b443b601/FileTest.txt' => true], $granted); + $this->logOut(); + + // Login as member with 'VIEW_DRAFT_CONTENT' permisson to access to file and get session access + $this->logInWithPermission('VIEW_DRAFT_CONTENT'); + $this->assertEquals('/assets/55b443b601/FileTest.txt', $rootfile->getURL()); + + $granted = $session->get(FlysystemAssetStore::GRANTS_SESSION); + $this->assertEquals(['55b443b601/FileTest.txt' => true], $granted); + + // Login as member of another Group that doesn't have permisson to access to file + // and don't grant session access $rootfile->publishSingle(); + $this->logOut(); + $this->logInWithPermission('SOME_PERMISSIONS'); + $this->assertEquals('/assets/FileTest/FileTest.txt', $rootfile->getURL()); + + $session = Controller::curr()->getRequest()->getSession(); + $granted = $session->get(FlysystemAssetStore::GRANTS_SESSION); + $this->assertNull($granted); + } + + public function testGetSourceURL() + { + /** @var File $rootfile */ + $rootfile = $this->objFromFixture(File::class, 'asdf'); + + // Links to incorrect base (assets/ rather than assets/FileTest) + // because ProtectedAdapter doesn't know about custom base dirs in TestAssetStore + $this->assertEquals('/assets/55b443b601/FileTest.txt', $rootfile->getSourceURL()); + + // Login as ADMIN and grant session access by default + $session = Controller::curr()->getRequest()->getSession(); + $granted = $session->get(FlysystemAssetStore::GRANTS_SESSION); + + $this->assertEquals(['55b443b601/FileTest.txt' => true], $granted); + $this->logOut(); + + // Login as member with 'VIEW_DRAFT_CONTENT' permisson to access to file and get session access + $this->logInWithPermission('VIEW_DRAFT_CONTENT'); + $this->assertEquals('/assets/55b443b601/FileTest.txt', $rootfile->getURL()); + + $granted = $session->get(FlysystemAssetStore::GRANTS_SESSION); + $this->assertEquals(['55b443b601/FileTest.txt' => true], $granted); + + // Login as member of another Group that doesn't have permisson to access to file + // and don't grant session access + $rootfile->publishSingle(); + $this->logOut(); + $this->logInWithPermission('SOME_PERMISSIONS'); + + $this->assertEquals('/assets/FileTest/FileTest.txt', $rootfile->getSourceURL()); + + $session = Controller::curr()->getRequest()->getSession(); + $granted = $session->get(FlysystemAssetStore::GRANTS_SESSION); + $this->assertNull($granted); } public function testGetAbsoluteURL() diff --git a/tests/php/Shortcodes/ImageShortcodeProviderTest.php b/tests/php/Shortcodes/ImageShortcodeProviderTest.php index 52194414..647ce8a4 100644 --- a/tests/php/Shortcodes/ImageShortcodeProviderTest.php +++ b/tests/php/Shortcodes/ImageShortcodeProviderTest.php @@ -256,6 +256,15 @@ public function testRegenerateShortcode() $html = ImageShortcodeProvider::regenerate_shortcode($args, '', '', 'image'); $this->assertSame($expected, $html); $this->assertFalse($assetStore->isGranted($parsedFileID)); + + // Login as member with 'VIEW_DRAFT_CONTENT' permisson to access to file and get session access + $this->logOut(); + + $this->logInWithPermission('VIEW_DRAFT_CONTENT'); + // Provide permissions to view file for any logged in users + $image->CanViewType = InheritedPermissions::LOGGED_IN_USERS; + $image->write(); + Config::modify()->set(FileShortcodeProvider::class, 'allow_session_grant', true); $html = ImageShortcodeProvider::regenerate_shortcode($args, '', '', 'image'); $this->assertSame($expected, $html); From a2733b90fae4f441ded07ce526621e13822c6ae8 Mon Sep 17 00:00:00 2001 From: Sabina Talipova <87288324+sabina-talipova@users.noreply.github.com> Date: Mon, 31 Oct 2022 16:27:11 +1300 Subject: [PATCH 06/32] DEP Upgrade legue/flysystem to version 3.0 (#524) --- _config/asset.yml | 4 +- composer.json | 2 +- .../Tasks/LegacyThumbnailMigrationHelper.php | 8 +-- .../Tasks/NormaliseAccessMigrationHelper.php | 20 +++--- src/Dev/TestAssetStore.php | 14 ++--- .../FileIDHelperResolutionStrategy.php | 5 +- .../FileResolutionStrategy.php | 2 +- src/Flysystem/AssetAdapter.php | 29 +++++---- src/Flysystem/Filesystem.php | 35 +++++++++++ src/Flysystem/FlysystemAssetStore.php | 63 ++++++++++--------- src/Flysystem/GeneratedAssets.php | 24 +++---- src/Flysystem/LocalFilesystemAdapter.php | 29 +++++++++ src/Flysystem/ProtectedAdapter.php | 4 +- src/Flysystem/PublicAdapter.php | 4 +- src/Storage/FileHashingService.php | 4 +- src/Storage/Sha1FileHashingService.php | 4 +- src/Util.php | 28 +++++++++ .../php/Dev/Tasks/FileMigrationHelperTest.php | 14 ++--- .../Tasks/SecureAssetsMigrationHelperTest.php | 2 +- .../FileIDHelperResolutionStrategyTest.php | 22 +++---- tests/php/Flysystem/AssetAdapterTest.php | 5 +- .../php/Flysystem/FlysystemAssetStoreTest.php | 6 +- tests/php/ProtectedFileControllerTest.php | 2 +- tests/php/RedirectFileControllerTest.php | 2 +- tests/php/Storage/AssetStoreTest.php | 14 ++--- .../Storage/Sha1FileHashingServiceTest.php | 21 ++----- 26 files changed, 225 insertions(+), 142 deletions(-) create mode 100644 src/Flysystem/Filesystem.php create mode 100644 src/Flysystem/LocalFilesystemAdapter.php create mode 100644 src/Util.php diff --git a/_config/asset.yml b/_config/asset.yml index 173dddae..e7a165b2 100644 --- a/_config/asset.yml +++ b/_config/asset.yml @@ -10,14 +10,14 @@ SilverStripe\Core\Injector\Injector: class: SilverStripe\Assets\Flysystem\ProtectedAssetAdapter # Define the default filesystem League\Flysystem\Filesystem.public: - class: League\Flysystem\Filesystem + class: SilverStripe\Assets\Flysystem\Filesystem constructor: FilesystemAdapter: '%$SilverStripe\Assets\Flysystem\PublicAdapter' FilesystemConfig: visibility: public # Define the secondary filesystem for protected assets League\Flysystem\Filesystem.protected: - class: League\Flysystem\Filesystem + class: SilverStripe\Assets\Flysystem\Filesystem constructor: FilesystemAdapter: '%$SilverStripe\Assets\Flysystem\ProtectedAdapter' FilesystemConfig: diff --git a/composer.json b/composer.json index f72566d3..2c479de0 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "silverstripe/vendor-plugin": "^2", "symfony/filesystem": "^6.1", "intervention/image": "^2.7.2", - "league/flysystem": "^1.1.9" + "league/flysystem": "^3.9.0" }, "require-dev": { "silverstripe/recipe-testing": "^3", diff --git a/src/Dev/Tasks/LegacyThumbnailMigrationHelper.php b/src/Dev/Tasks/LegacyThumbnailMigrationHelper.php index b6681168..fe6e6987 100644 --- a/src/Dev/Tasks/LegacyThumbnailMigrationHelper.php +++ b/src/Dev/Tasks/LegacyThumbnailMigrationHelper.php @@ -146,7 +146,7 @@ protected function migrateFolder(FlysystemAssetStore $store, Folder $folder) $foundError = false; // Recurse through folder - foreach ($filesystem->listContents($resampledFolderPath, true) as $fileInfo) { + foreach ($filesystem->listContents($resampledFolderPath, true)->toArray() as $fileInfo) { if ($fileInfo['type'] !== 'file') { continue; } @@ -179,7 +179,7 @@ protected function migrateFolder(FlysystemAssetStore $store, Folder $folder) continue; } - $filesystem->rename($oldResampledPath, $newResampledPath); + $filesystem->move($oldResampledPath, $newResampledPath); $this->logger->info(sprintf('Moved legacy thumbnail %s to %s', $oldResampledPath, $newResampledPath)); @@ -190,13 +190,13 @@ protected function migrateFolder(FlysystemAssetStore $store, Folder $folder) // get migrated leave the folder where it is. if (!$foundError) { $files = array_filter( - $filesystem->listContents($resampledFolderPath, true) ?? [], + $filesystem->listContents($resampledFolderPath, true)->toArray() ?? [], function ($file) { return $file['type'] === 'file'; } ); if (empty($files)) { - $filesystem->deleteDir($resampledFolderPath); + $filesystem->deleteDirectory($resampledFolderPath); } else { // This should not be possible. If it is, then there's probably a bug. $this->logger->error(sprintf( diff --git a/src/Dev/Tasks/NormaliseAccessMigrationHelper.php b/src/Dev/Tasks/NormaliseAccessMigrationHelper.php index b251d3b1..e23fcfc6 100644 --- a/src/Dev/Tasks/NormaliseAccessMigrationHelper.php +++ b/src/Dev/Tasks/NormaliseAccessMigrationHelper.php @@ -4,7 +4,7 @@ use Exception; use InvalidArgumentException; use League\Flysystem\Filesystem; -use League\Flysystem\FilesystemInterface; +use League\Flysystem\FilesystemOperator; use LogicException; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -290,7 +290,7 @@ private function cleanup() foreach ($this->public_folders_to_truncate as $path) { if ($this->canBeTruncated($path, $fs)) { $this->info(sprintf('Deleting empty folder %s', $path)); - $fs->deleteDir($path); + $fs->deleteDirectory($path); $truncatedPaths[] = $path; } } @@ -308,17 +308,17 @@ private function cleanup() /** * Check if the provided folder can be deleted on this Filesystem * @param string $path - * @param FilesystemInterface $fs + * @param FilesystemOperator $fs * @return bool */ - private function canBeTruncated($path, FilesystemInterface $fs) + private function canBeTruncated($path, FilesystemOperator $fs) { - if (!$fs->has($path)) { + if (!$fs->directoryExists($path)) { // The folder doesn't exists return false; } - $contents = $fs->listContents($path); + $contents = $fs->listContents($path, true)->toArray(); foreach ($contents as $content) { if ($content['type'] !== 'dir') { @@ -341,14 +341,14 @@ private function canBeTruncated($path, FilesystemInterface $fs) /** * Delete this folder if it doesn't contain any files and parent folders if they don't contain any files either. * @param string $path - * @param FilesystemInterface $fs + * @param FilesystemOperator $fs */ - private function recursiveTruncate($path, FilesystemInterface $fs) + private function recursiveTruncate($path, FilesystemOperator $fs) { - if ($path && ltrim($path ?? '', '.') && empty($fs->listContents($path)) + if ($path && ltrim($path ?? '', '.') && empty($fs->listContents($path)->toArray()) ) { $this->info(sprintf('Deleting empty folder %s', $path)); - $fs->deleteDir($path); + $fs->deleteDirectory($path); $this->recursiveTruncate(dirname($path ?? ''), $fs); } } diff --git a/src/Dev/TestAssetStore.php b/src/Dev/TestAssetStore.php index c4f354df..693a4e43 100644 --- a/src/Dev/TestAssetStore.php +++ b/src/Dev/TestAssetStore.php @@ -2,11 +2,10 @@ namespace SilverStripe\Assets\Dev; -use League\Flysystem\Adapter\Local; -use League\Flysystem\AdapterInterface; -use League\Flysystem\Filesystem; +use League\Flysystem\Visibility; use SilverStripe\Assets\FilenameParsing\FileResolutionStrategy; use SilverStripe\Assets\Filesystem as SSFilesystem; +use SilverStripe\Assets\Flysystem\LocalFilesystemAdapter; use SilverStripe\Assets\Flysystem\FlysystemAssetStore; use SilverStripe\Assets\Flysystem\ProtectedAssetAdapter; use SilverStripe\Assets\Flysystem\PublicAssetAdapter; @@ -16,6 +15,7 @@ use SilverStripe\Assets\Storage\AssetStoreRouter; use SilverStripe\Assets\Storage\DBFile; use SilverStripe\Assets\File; +use SilverStripe\Assets\Flysystem\Filesystem; use SilverStripe\Assets\Folder; use SilverStripe\Assets\Storage\GeneratedAssetHandler; use SilverStripe\Control\Controller; @@ -65,14 +65,14 @@ public static function activate($basedir) $publicFilesystem = new Filesystem( $publicAdapter, [ - 'visibility' => AdapterInterface::VISIBILITY_PUBLIC + 'visibility' => Visibility::PUBLIC ] ); $protectedAdapter = new ProtectedAssetAdapter(ASSETS_PATH . '/' . $basedir . '/.protected'); $protectedFilesystem = new Filesystem( $protectedAdapter, [ - 'visibility' => AdapterInterface::VISIBILITY_PRIVATE + 'visibility' => Visibility::PRIVATE ] ); @@ -156,9 +156,9 @@ public static function getLocalPath(AssetContainer $asset, $forceProtected = fal if (!$forceProtected && !$filesystem->has($fileID)) { $filesystem = $assetStore->getPublicFilesystem(); } - /** @var Local $adapter */ + /** @var LocalFilesystemAdapter $adapter */ $adapter = $filesystem->getAdapter(); - return $relative ? $fileID : $adapter->applyPathPrefix($fileID); + return $relative ? $fileID : $adapter->prefixPath($fileID); } public function cleanFilename($filename) diff --git a/src/FilenameParsing/FileIDHelperResolutionStrategy.php b/src/FilenameParsing/FileIDHelperResolutionStrategy.php index 5a183e0b..591df5c9 100644 --- a/src/FilenameParsing/FileIDHelperResolutionStrategy.php +++ b/src/FilenameParsing/FileIDHelperResolutionStrategy.php @@ -4,6 +4,7 @@ use InvalidArgumentException; use League\Flysystem\Filesystem; +use League\Flysystem\UnableToCheckExistence; use SilverStripe\Assets\Storage\FileHashingService; use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Injector\Injectable; @@ -298,7 +299,7 @@ private function validateHash(FileIDHelper $helper, ParsedFileID $parsedFileID, * @param ParsedFileID $parsedFileID * @param Filesystem $filesystem * @return bool|string - * @throws \League\Flysystem\FileNotFoundException + * @throws UnableToCheckExistence */ private function findHashOf(FileIDHelper $helper, ParsedFileID $parsedFileID, Filesystem $filesystem) { @@ -436,7 +437,7 @@ public function findVariants($tuple, Filesystem $filesystem) // Find the correct folder to search for possible variants in $folder = $helper->lookForVariantIn($parsedFileID); - $possibleVariants = $filesystem->listContents($folder, $helper->lookForVariantRecursive()); + $possibleVariants = $filesystem->listContents($folder, $helper->lookForVariantRecursive())->toArray(); // Flysystem returns array of meta data abouch each file, we remove directories and map it down to the path $possibleVariants = array_filter($possibleVariants ?? [], function ($possibleVariant) { diff --git a/src/FilenameParsing/FileResolutionStrategy.php b/src/FilenameParsing/FileResolutionStrategy.php index 776543fc..c3ec11c3 100644 --- a/src/FilenameParsing/FileResolutionStrategy.php +++ b/src/FilenameParsing/FileResolutionStrategy.php @@ -68,7 +68,7 @@ public function parseFileID($fileID); * @param array|ParsedFileID $tuple * @param Filesystem $filesystem * @return generator|ParsedFileID[]|null - * @throws \League\Flysystem\FileNotFoundException + * @throws \League\Flysystem\UnableToCheckExistence */ public function findVariants($tuple, Filesystem $filesystem); diff --git a/src/Flysystem/AssetAdapter.php b/src/Flysystem/AssetAdapter.php index d6c1d3fd..c5c4b1e6 100644 --- a/src/Flysystem/AssetAdapter.php +++ b/src/Flysystem/AssetAdapter.php @@ -2,9 +2,9 @@ namespace SilverStripe\Assets\Flysystem; -use Exception; -use League\Flysystem\Adapter\Local; use League\Flysystem\Config as FlysystemConfig; +use League\Flysystem\UnableToWriteFile; +use League\Flysystem\UnixVisibility\PortableVisibilityConverter; use SilverStripe\Assets\File; use SilverStripe\Assets\Filesystem; use SilverStripe\Core\Config\Config; @@ -16,7 +16,7 @@ /** * Adapter for local filesystem based on assets directory */ -class AssetAdapter extends Local +class AssetAdapter extends LocalFilesystemAdapter { use Configurable; @@ -61,8 +61,10 @@ public function __construct($root = null, $writeFlags = LOCK_EX, $linkHandling = $root = realpath($root ?? ''); // Override permissions with config - $permissions = $this->normalisePermissions($this->config()->get('file_permissions')); - parent::__construct($root, $writeFlags, $linkHandling, $permissions); + $permissions = PortableVisibilityConverter::fromArray( + $this->normalisePermissions($this->config()->get('file_permissions')) + ); + parent::__construct($root, $permissions, $writeFlags, $linkHandling); // Configure server $this->configureServer(); @@ -124,7 +126,7 @@ public function flush() * Configure server files for this store * * @param bool $forceOverwrite Force regeneration even if files already exist - * @throws Exception + * @throws UnableToWriteFile */ protected function configureServer($forceOverwrite = false) { @@ -146,18 +148,19 @@ protected function configureServer($forceOverwrite = false) // Apply each configuration $config = new FlysystemConfig(); - $config->set('visibility', $visibility); + $config->extend(['visibility' => $visibility]); foreach ($configurations as $file => $template) { // Ensure file contents - if ($forceOverwrite || !$this->has($file)) { + if ($forceOverwrite || !$this->fileExists($file)) { // Evaluate file - $content = $this->renderTemplate($template); - $success = $this->write($file, $content, $config); - if (!$success) { - throw new Exception("Error writing server configuration file \"{$file}\""); + try { + $content = $this->renderTemplate($template); + $this->write($file, $content, $config); + } catch (UnableToWriteFile $exception) { + throw UnableToWriteFile::atLocation($file, $exception->reason(), $exception); } } - $perms = $this->getVisibility($file); + $perms = $this->visibility($file); if ($perms['visibility'] !== $visibility) { // Ensure correct permissions $this->setVisibility($file, $visibility); diff --git a/src/Flysystem/Filesystem.php b/src/Flysystem/Filesystem.php new file mode 100644 index 00000000..3fa0e55f --- /dev/null +++ b/src/Flysystem/Filesystem.php @@ -0,0 +1,35 @@ +adapter = $adapter; + $this->pathNormalizer = $pathNormalizer ?: new WhitespacePathNormalizer(); + parent::__construct($adapter, $config, $pathNormalizer); + } + + public function getAdapter(): FilesystemAdapter + { + return $this->adapter; + } + + public function has(string $location): bool + { + $path = $this->pathNormalizer->normalizePath($location); + + return strlen($path) === 0 ? false : ($this->getAdapter()->fileExists($path) || $this->getAdapter()->directoryExists($path)); + } +} diff --git a/src/Flysystem/FlysystemAssetStore.php b/src/Flysystem/FlysystemAssetStore.php index 1fd076f6..834d2f05 100644 --- a/src/Flysystem/FlysystemAssetStore.php +++ b/src/Flysystem/FlysystemAssetStore.php @@ -2,14 +2,12 @@ namespace SilverStripe\Assets\Flysystem; +use Exception; use Generator; use InvalidArgumentException; -use League\Flysystem\Directory; -use League\Flysystem\Exception as FlysystemException; -use League\Flysystem\Filesystem; -use League\Flysystem\Util; use LogicException; use SilverStripe\Assets\File; +use SilverStripe\Assets\Util; use SilverStripe\Assets\FilenameParsing\FileIDHelper; use SilverStripe\Assets\FilenameParsing\FileResolutionStrategy; use SilverStripe\Assets\FilenameParsing\HashFileIDHelper; @@ -581,7 +579,7 @@ public function setFromStream($stream, $filename, $hash = null, $variant = null, } } - $result = $filesystem->putStream($fileID, $stream); + $result = $filesystem->writeStream($fileID, $stream); // If we have an hash for a main file, let's pre-warm our file hashing cache. if ($hash || !$variant) { @@ -640,7 +638,7 @@ function (ParsedFileID $parsedFileID, Filesystem $fs, FileResolutionStrategy $st // Invalidate hash of delete file $hasher->invalidate($origin, $fs); } else { - $fs->rename($origin, $destination); + $fs->move($origin, $destination); // Move cached hash value to new location $hasher->move($origin, $fs, $destination); } @@ -754,9 +752,9 @@ protected function truncateDirectory($dirname, Filesystem $filesystem) if ($dirname && ltrim($dirname ?? '', '.') && !$this->config()->get('keep_empty_dirs') - && !$filesystem->listContents($dirname) + && !$filesystem->listContents($dirname)->toArray() ) { - $filesystem->deleteDir($dirname); + $filesystem->deleteDirectory($dirname); $this->truncateDirectory(dirname($dirname ?? ''), $filesystem); } } @@ -772,7 +770,7 @@ protected function truncateDirectory($dirname, Filesystem $filesystem) protected function findVariants($fileID, Filesystem $filesystem) { $dirname = ltrim(dirname($fileID ?? ''), '.'); - foreach ($filesystem->listContents($dirname) as $next) { + foreach ($filesystem->listContents($dirname)->toArray() as $next) { if ($next['type'] !== 'file') { continue; } @@ -840,7 +838,7 @@ public function swapPublish($filename, $hash) // Cache destination file into the origin store under a `.swap` directory $stream = $to->readStream($toFileID); - $from->putStream('.swap/' . $fromFileID, $stream); + $from->writeStream('.swap/' . $fromFileID, $stream); if (is_resource($stream)) { fclose($stream); } @@ -862,7 +860,7 @@ public function swapPublish($filename, $hash) $toFileID = $toStrategy->buildFileID($variantParsedFileID); $stream = $from->readStream($fromFileID); - $to->putStream($toFileID, $stream); + $to->writeStream($toFileID, $stream); if (is_resource($stream)) { fclose($stream); } @@ -875,10 +873,10 @@ public function swapPublish($filename, $hash) foreach ($swapFiles as $variantParsedFileID) { $fileID = $variantParsedFileID->getFileID(); - $from->rename('.swap/' . $fileID, $fileID); + $from->move('.swap/' . $fileID, $fileID); $hasher->move('.swap/' . $fileID, $from, $fileID); } - $from->deleteDir('.swap'); + $from->deleteDirectory('.swap'); } public function protect($filename, $hash) @@ -916,7 +914,7 @@ protected function moveBetweenFilesystems($fileID, Filesystem $from, Filesystem foreach ($this->findVariants($fileID, $from) as $nextID) { // Copy via stream $stream = $from->readStream($nextID); - $to->putStream($nextID, $stream); + $to->writeStream($nextID, $stream); if (is_resource($stream)) { fclose($stream); } @@ -954,7 +952,7 @@ protected function moveBetweenFileStore( $toFileID = $toStrategy->buildFileID($variantParsedFileID); $stream = $from->readStream($fromFileID); - $to->putStream($toFileID, $stream); + $to->writeStream($toFileID, $stream); if (is_resource($stream)) { fclose($stream); } @@ -1071,7 +1069,7 @@ protected function getStreamSHA1($stream) * * @param resource $stream * @return string Filename of resulting stream content - * @throws FlysystemException + * @throws Exception */ protected function getStreamAsFile($stream) { @@ -1079,14 +1077,14 @@ protected function getStreamAsFile($stream) $file = tempnam(sys_get_temp_dir(), 'ssflysystem'); $buffer = fopen($file ?? '', 'w'); if (!$buffer) { - throw new FlysystemException("Could not create temporary file"); + throw new Exception("Could not create temporary file"); } // Transfer from given stream Util::rewindStream($stream); stream_copy_to_stream($stream, $buffer); if (!fclose($buffer)) { - throw new FlysystemException("Could not write stream to temporary file"); + throw new Exception("Could not write stream to temporary file"); } return $file; @@ -1113,7 +1111,7 @@ protected function isSeekableStream($stream) * @param string $variant Variant to write * @param array $config Write options. {@see AssetStore} * @return array Tuple associative array (Filename, Hash, Variant) - * @throws FlysystemException + * @throws InvalidArgumentException */ protected function writeWithCallback($callback, $filename, $hash, $variant = null, $config = []) { @@ -1198,10 +1196,7 @@ function ( } // Submit and validate result - $result = $callback($fs, $parsedFileID->getFileID()); - if (!$result) { - throw new FlysystemException("Could not save {$filename}"); - } + $callback($fs, $parsedFileID->getFileID()); return $parsedFileID->getTuple(); } @@ -1233,7 +1228,15 @@ public function getMetadata($filename, $hash, $variant = null) // If `applyToFileOnFilesystem` calls our closure we'll know for sure that a file exists return $this->applyToFileOnFilesystem( function (ParsedFileID $parsedFileID, Filesystem $fs) { - return $fs->getMetadata($parsedFileID->getFileID()); + $path = $parsedFileID->getFileID(); + return [ + 'timestamp' => $fs->lastModified($path), + 'fileExists' => $fs->fileExists($path), + 'directoryExists' => $fs->directoryExists($path), + 'type' => $fs->mimeType($path), + 'size' => $fs->fileSize($path), + 'visibility' => $fs->visibility($path), + ]; }, new ParsedFileID($filename, $hash, $variant) ); @@ -1244,7 +1247,7 @@ public function getMimeType($filename, $hash, $variant = null) // If `applyToFileOnFilesystem` calls our closure we'll know for sure that a file exists return $this->applyToFileOnFilesystem( function (ParsedFileID $parsedFileID, Filesystem $fs) { - return $fs->getMimetype($parsedFileID->getFileID()); + return $fs->mimetype($parsedFileID->getFileID()); }, new ParsedFileID($filename, $hash, $variant) ); @@ -1551,14 +1554,14 @@ private function generateResponseFor(string $asset): array protected function createResponseFor(Filesystem $flysystem, $fileID) { // Block directory access - if ($flysystem->get($fileID) instanceof Directory) { + if ($flysystem->directoryExists($fileID)) { return $this->createDeniedResponse(); } // Create streamable response $stream = $flysystem->readStream($fileID); - $size = $flysystem->getSize($fileID); - $mime = $flysystem->getMimetype($fileID); + $size = $flysystem->fileSize($fileID); + $mime = $flysystem->mimetype($fileID); $response = HTTPStreamResponse::create($stream, $size) ->addHeader('Content-Type', $mime); @@ -1655,7 +1658,7 @@ function (...$args) { * @param FileResolutionStrategy $strategy * @return array List of new file names with the old name as the key * @throws \League\Flysystem\FileExistsException - * @throws \League\Flysystem\FileNotFoundException + * @throws \League\Flysystem\UnableToCheckExistence */ private function normaliseToDefaultPath(ParsedFileID $pfid, Filesystem $fs, FileResolutionStrategy $strategy) { @@ -1689,7 +1692,7 @@ private function normaliseToDefaultPath(ParsedFileID $pfid, Filesystem $fs, File $fs->delete($origin); $hasher->invalidate($origin, $fs); } else { - $fs->rename($origin, $targetVariantFileID); + $fs->move($origin, $targetVariantFileID); $hasher->move($origin, $fs, $targetVariantFileID); $ops[$origin] = $targetVariantFileID; } diff --git a/src/Flysystem/GeneratedAssets.php b/src/Flysystem/GeneratedAssets.php index c0f4dd6d..b56a2f63 100644 --- a/src/Flysystem/GeneratedAssets.php +++ b/src/Flysystem/GeneratedAssets.php @@ -3,8 +3,8 @@ namespace SilverStripe\Assets\Flysystem; use Exception; -use League\Flysystem\File; -use League\Flysystem\Filesystem; +use SilverStripe\Assets\Flysystem\Filesystem; +use League\Flysystem\UnableToWriteFile; use SilverStripe\Assets\Storage\GeneratedAssetHandler; /** @@ -80,7 +80,7 @@ public function getContent($filename, $callback = null) * @param string $filename * @param callable $callback * @return bool Whether or not the file exists - * @throws Exception If an error has occurred during save + * @throws UnableToWriteFile If an error has occurred during save */ protected function checkOrCreate($filename, $callback = null) { @@ -102,21 +102,17 @@ protected function checkOrCreate($filename, $callback = null) public function setContent($filename, $content) { // Store content - $result = $this - ->getFilesystem() - ->put($filename, $content); - - if (!$result) { - throw new Exception("Error regenerating file \"{$filename}\""); - } + $this->getFilesystem()->write($filename, $content); } public function removeContent($filename) { - if ($this->getFilesystem()->has($filename)) { - /** @var File $handler */ - $handler = $this->getFilesystem()->get($filename); - $handler->delete(); + $filesystem = $this->getFilesystem(); + + if ($filesystem->directoryExists($filename)) { + $filesystem->deleteDirectory($filename); + } elseif ($filesystem->fileExists($filename)) { + $filesystem->delete($filename); } } } diff --git a/src/Flysystem/LocalFilesystemAdapter.php b/src/Flysystem/LocalFilesystemAdapter.php new file mode 100644 index 00000000..bd41de63 --- /dev/null +++ b/src/Flysystem/LocalFilesystemAdapter.php @@ -0,0 +1,29 @@ +pathPrefixer = new PathPrefixer($location); + parent::__construct($location, $visibility, $writeFlags, $linkHandling, $mimeTypeDetector); + } + + public function prefixPath(string $path): string + { + return $this->pathPrefixer->prefixPath($path); + } +} diff --git a/src/Flysystem/ProtectedAdapter.php b/src/Flysystem/ProtectedAdapter.php index 33f76df3..c6db532b 100644 --- a/src/Flysystem/ProtectedAdapter.php +++ b/src/Flysystem/ProtectedAdapter.php @@ -2,12 +2,12 @@ namespace SilverStripe\Assets\Flysystem; -use League\Flysystem\AdapterInterface; +use League\Flysystem\FilesystemAdapter; /** * An adapter which does not publicly expose protected files */ -interface ProtectedAdapter extends AdapterInterface +interface ProtectedAdapter extends FilesystemAdapter { /** diff --git a/src/Flysystem/PublicAdapter.php b/src/Flysystem/PublicAdapter.php index 0770e246..3722b3ee 100644 --- a/src/Flysystem/PublicAdapter.php +++ b/src/Flysystem/PublicAdapter.php @@ -2,12 +2,12 @@ namespace SilverStripe\Assets\Flysystem; -use League\Flysystem\AdapterInterface; +use League\Flysystem\FilesystemAdapter; /** * Represents an AbstractAdapter which exposes its assets via public urls */ -interface PublicAdapter extends AdapterInterface +interface PublicAdapter extends FilesystemAdapter { /** diff --git a/src/Storage/FileHashingService.php b/src/Storage/FileHashingService.php index 09b48800..8ea3bd6c 100644 --- a/src/Storage/FileHashingService.php +++ b/src/Storage/FileHashingService.php @@ -2,7 +2,7 @@ namespace SilverStripe\Assets\Storage; -use League\Flysystem\FileNotFoundException; +use League\Flysystem\UnableToCheckExistence; use League\Flysystem\Filesystem; /** @@ -27,7 +27,7 @@ public function computeFromStream($stream); * @param string $fileID * @param Filesystem|string $fs * @return string - * @throws FileNotFoundException + * @throws UnableToCheckExistence */ public function computeFromFile($fileID, $fs); diff --git a/src/Storage/Sha1FileHashingService.php b/src/Storage/Sha1FileHashingService.php index ce3a95c1..ef8e0d83 100644 --- a/src/Storage/Sha1FileHashingService.php +++ b/src/Storage/Sha1FileHashingService.php @@ -4,8 +4,8 @@ use InvalidArgumentException; use League\Flysystem\Filesystem; -use League\Flysystem\Util; use Psr\SimpleCache\CacheInterface; +use SilverStripe\Assets\Util; use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Flushable; use SilverStripe\Core\Injector\Injectable; @@ -195,7 +195,7 @@ private function getTimestamp($fileID, $fs) { $filesystem = $this->getFilesystem($fs); return $filesystem->has($fileID) ? - $filesystem->getTimestamp($fileID) : + $filesystem->lastModified($fileID) : DBDatetime::now()->getTimestamp(); } diff --git a/src/Util.php b/src/Util.php new file mode 100644 index 00000000..53c7eb1e --- /dev/null +++ b/src/Util.php @@ -0,0 +1,28 @@ +generateFilename(); rewind($fromMain); - $fs->write($filename, $fromMain); + $fs->writeStream($filename, $fromMain); $dir = dirname($filename ?? ''); $basename = basename($filename ?? ''); rewind($fromVariant); - $fs->write($dir . '/_resampled/resizeXYZ/' . $basename, $fromVariant); + $fs->writeStream($dir . '/_resampled/resizeXYZ/' . $basename, $fromVariant); rewind($fromVariant); - $fs->write($dir . '/_resampled/resizeXYZ/scaleABC/' . $basename, $fromVariant); + $fs->writeStream($dir . '/_resampled/resizeXYZ/scaleABC/' . $basename, $fromVariant); rewind($fromVariant); - $fs->write($dir . '/_resampled/ScaleWidthWzEwMF0-' . $basename, $fromVariant); + $fs->writeStream($dir . '/_resampled/ScaleWidthWzEwMF0-' . $basename, $fromVariant); rewind($fromVariant); - $fs->write($dir . '/_resampled/ScaleWidthWzEwMF0-FitWzEwMCwxMDBd-' . $basename, $fromVariant); + $fs->writeStream($dir . '/_resampled/ScaleWidthWzEwMF0-FitWzEwMCwxMDBd-' . $basename, $fromVariant); } fclose($fromMain); fclose($fromVariant); - $fs->rename('wrong-case.txt', 'wRoNg-CaSe.tXt'); - $fs->rename('Uploads/good-case-bad-folder.txt', 'uploads/good-case-bad-folder.txt'); + $fs->move('wrong-case.txt', 'wRoNg-CaSe.tXt'); + $fs->move('Uploads/good-case-bad-folder.txt', 'uploads/good-case-bad-folder.txt'); $fs->copy('too-many-alternative-case.txt', 'Too-Many-Alternative-Case.txt'); $fs->copy('too-many-alternative-case.txt', 'Too-Many-Alternative-Case.TXT'); $fs->delete('too-many-alternative-case.txt'); diff --git a/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.php b/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.php index f74c1bf6..40d4de32 100644 --- a/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.php +++ b/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.php @@ -112,7 +112,7 @@ public function dataMigrate() ], 'Unprotected' => [ 'unprotected', - null, + '', false ], 'Protected with modified htaccess' => [ diff --git a/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php b/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php index f396fd59..5e95c3f5 100644 --- a/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php +++ b/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php @@ -1,7 +1,6 @@ tmpFolder ?? ''); $this->fs = new Filesystem( - new Local($this->tmpFolder) + new LocalFilesystemAdapter($this->tmpFolder) ); Injector::inst()->registerService($this->fs, Filesystem::class . '.public'); } @@ -54,9 +54,9 @@ protected function tearDown(): void TestAssetStore::reset(); // Clean up our temp adapter - foreach ($this->fs->listContents() as $fileMeta) { + foreach ($this->fs->listContents('')->toArray() as $fileMeta) { if ($fileMeta['type'] === 'dir') { - $this->fs->deleteDir($fileMeta['path']); + $this->fs->deleteDirectory($fileMeta['path']); } else { $this->fs->delete($fileMeta['path']); } @@ -372,7 +372,7 @@ public function testSearchForTuple(FileIDHelperResolutionStrategy $strategy, $tu $found = $strategy->searchForTuple($tuple, $this->fs, true); $this->assertEquals($expected, $found->getFileID(), 'The file has been written'); - $this->fs->put($expected, 'the hash will change and will not match our tuple'); + $this->fs->write($expected, 'the hash will change and will not match our tuple'); $found = $strategy->searchForTuple($tuple, $this->fs, false); $this->assertEquals( @@ -434,8 +434,8 @@ public function testHashlessSearchForTuple() $this->assertEquals($hash, $respPfID->getHash(), 'hash should have been read from main file'); // Looking for hash path of file NOT in DB - $fs->rename($naturalPath, $hashPath); - $fs->rename($variantNaturalPath, $variantHashPath); + $fs->move($naturalPath, $hashPath); + $fs->move($variantNaturalPath, $variantHashPath); $this->assertNull($strategy->searchForTuple($pfID, $fs), 'strategy does not know in what folder to look'); $this->assertNull($strategy->searchForTuple($variantPfID, $fs), 'strategy does not know in what folder to look'); @@ -523,9 +523,9 @@ public function testFindVariant($strategy, $tuple) $this->fs->write('RootFile.txt', 'version 1'); $expectedPaths = [ - 'Folder/FolderFile.pdf', 'Folder/FolderFile__mockedvariant.pdf', - 'Folder/SubFolder/SubFolderFile.pdf' + 'Folder/FolderFile.pdf', + 'Folder/SubFolder/SubFolderFile.pdf', ]; $variantGenerator = $strategy->findVariants($tuple, $this->fs); @@ -557,8 +557,8 @@ public function testFindHashlessVariant() $this->fs->write('Folder/FolderFile__mockedvariant.pdf', 'version 1 -- mockedvariant'); $expectedPaths = [ - ['Folder/FolderFile.pdf', ''], - ['Folder/FolderFile__mockedvariant.pdf', 'mockedvariant'] + ['Folder/FolderFile__mockedvariant.pdf', 'mockedvariant'], + ['Folder/FolderFile.pdf', ''] // The hash path won't be match, because we're not providing a hash ]; diff --git a/tests/php/Flysystem/AssetAdapterTest.php b/tests/php/Flysystem/AssetAdapterTest.php index 910d858a..2206fadf 100644 --- a/tests/php/Flysystem/AssetAdapterTest.php +++ b/tests/php/Flysystem/AssetAdapterTest.php @@ -58,8 +58,7 @@ public function testPublicAdapter() $this->assertFileExists($this->rootDir . '/.htaccess'); $this->assertFileDoesNotExist($this->rootDir . '/web.config'); - $htaccess = $adapter->read('.htaccess'); - $content = $htaccess['contents']; + $content = $adapter->read('.htaccess'); // Allowed extensions set $this->assertStringContainsString('RewriteCond %{REQUEST_URI} !^[^.]*[^\/]*\.(?i:', $content); foreach (File::getAllowedExtensions() as $extension) { @@ -74,7 +73,7 @@ public function testPublicAdapter() file_put_contents($this->rootDir . '/.htaccess', '# broken content'); $adapter->flush(); $htaccess2 = $adapter->read('.htaccess'); - $this->assertEquals($content, $htaccess2['contents']); + $this->assertEquals($content, $htaccess2); // Test URL $this->assertEquals('/assets/AssetAdapterTest/file.jpg', $adapter->getPublicUrl('file.jpg')); diff --git a/tests/php/Flysystem/FlysystemAssetStoreTest.php b/tests/php/Flysystem/FlysystemAssetStoreTest.php index 76168cf6..029eb415 100644 --- a/tests/php/Flysystem/FlysystemAssetStoreTest.php +++ b/tests/php/Flysystem/FlysystemAssetStoreTest.php @@ -2,9 +2,9 @@ namespace SilverStripe\Assets\Tests\Flysystem; -use League\Flysystem\Filesystem; use SilverStripe\Assets\FilenameParsing\FileIDHelperResolutionStrategy; use SilverStripe\Assets\FilenameParsing\FileResolutionStrategy; +use SilverStripe\Assets\Flysystem\Filesystem; use SilverStripe\Assets\Flysystem\FlysystemAssetStore; use SilverStripe\Assets\Flysystem\ProtectedAssetAdapter; use SilverStripe\Assets\Flysystem\PublicAssetAdapter; @@ -49,7 +49,7 @@ protected function setUp(): void ->getMock(); $this->publicFilesystem = $this->getMockBuilder(Filesystem::class) - ->setMethods(['has', 'read', 'readStream', 'getTimestamp']) + ->setMethods(['has', 'read', 'readStream', 'lastModified']) ->setConstructorArgs([$this->publicAdapter]) ->getMock(); @@ -58,7 +58,7 @@ protected function setUp(): void ->getMock(); $this->protectedFilesystem = $this->getMockBuilder(Filesystem::class) - ->setMethods(['has', 'read', 'readStream', 'getTimestamp']) + ->setMethods(['has', 'read', 'readStream', 'lastModified']) ->setConstructorArgs([$this->protectedAdapter]) ->getMock(); diff --git a/tests/php/ProtectedFileControllerTest.php b/tests/php/ProtectedFileControllerTest.php index 0250b563..c4f0d6ec 100644 --- a/tests/php/ProtectedFileControllerTest.php +++ b/tests/php/ProtectedFileControllerTest.php @@ -349,7 +349,7 @@ protected function assertResponseEquals($code, $body, HTTPResponse $response) // 'text/plain' for test case pdfs in php7.4 + 8.0 , though in php8.1 it will // return 'application/octet-stream' which is then converted to 'application/pdf' // based on the file extension - $this->assertTrue(in_array($response->getHeader('Content-Type'), ['text/plain', 'application/pdf'])); + $this->assertTrue(in_array($response->getHeader('Content-Type'), ['text/plain', 'application/pdf', 'application/octet-stream'])); } else { $this->assertTrue($response->isError()); } diff --git a/tests/php/RedirectFileControllerTest.php b/tests/php/RedirectFileControllerTest.php index 5ff35abf..885e507a 100644 --- a/tests/php/RedirectFileControllerTest.php +++ b/tests/php/RedirectFileControllerTest.php @@ -141,7 +141,7 @@ public function testTemporaryFilenameRedirect($fixtureID) // This replicates a scenario where a file was publish under a hash path in SilverStripe 4.3 $store = $this->getAssetStore(); $fs = $store->getPublicFilesystem(); - $fs->rename($naturalUrl, $hashUrl); + $fs->move($naturalUrl, $hashUrl); $response = $this->get('assets/' . $naturalUrl); $this->assertResponse( diff --git a/tests/php/Storage/AssetStoreTest.php b/tests/php/Storage/AssetStoreTest.php index ca36f9da..26e9c5bd 100644 --- a/tests/php/Storage/AssetStoreTest.php +++ b/tests/php/Storage/AssetStoreTest.php @@ -419,7 +419,7 @@ public function testGetMetadata() ); $fishMeta = $backend->getMetadata($fishTuple['Filename'], $fishTuple['Hash']); $this->assertEquals(151889, $fishMeta['size']); - $this->assertEquals('file', $fishMeta['type']); + $this->assertEquals('image/jpeg', $fishMeta['type']); $this->assertNotEmpty($fishMeta['timestamp']); // text @@ -431,7 +431,7 @@ public function testGetMetadata() ); $puppiesMeta = $backend->getMetadata($puppiesTuple['Filename'], $puppiesTuple['Hash']); $this->assertEquals(7, $puppiesMeta['size']); - $this->assertEquals('file', $puppiesMeta['type']); + $this->assertEquals('text/plain', $puppiesMeta['type']); $this->assertNotEmpty($puppiesMeta['timestamp']); } @@ -891,7 +891,7 @@ public function testVariantWriteNextToFile( $variantParsedFileID->getVariant() ); - $this->assertTrue($fs->has($expectedVariantPath)); + $this->assertTrue($fs->fileExists($expectedVariantPath)); } public function listOfFilesToNormalise() @@ -990,12 +990,12 @@ public function testNormalise($fsName, array $contents, $filename, $hash, array $fs = $this->getFilesystem($fsName); foreach ($expected as $expectedFile) { - $this->assertTrue($fs->has($expectedFile), "$expectedFile should exists"); + $this->assertTrue($fs->fileExists($expectedFile), "$expectedFile should exists"); $this->assertNotEmpty($fs->read($expectedFile), "$expectedFile should be non empty"); } foreach ($notExpected as $notExpectedFile) { - $this->assertFalse($fs->has($notExpectedFile), "$notExpectedFile should NOT exists"); + $this->assertFalse($fs->fileExists($notExpectedFile), "$notExpectedFile should NOT exists"); } } @@ -1131,12 +1131,12 @@ public function testNormalisePath( $fs = $this->getFilesystem($fsName); foreach ($expected as $expectedFile) { - $this->assertTrue($fs->has($expectedFile), "$expectedFile should exists"); + $this->assertTrue($fs->fileExists($expectedFile), "$expectedFile should exists"); $this->assertNotEmpty($fs->read($expectedFile), "$expectedFile should be non empty"); } foreach ($notExpected as $notExpectedFile) { - $this->assertFalse($fs->has($notExpectedFile), "$notExpectedFile should NOT exists"); + $this->assertFalse($fs->fileExists($notExpectedFile), "$notExpectedFile should NOT exists"); } } diff --git a/tests/php/Storage/Sha1FileHashingServiceTest.php b/tests/php/Storage/Sha1FileHashingServiceTest.php index 0c04b749..6417a1a8 100644 --- a/tests/php/Storage/Sha1FileHashingServiceTest.php +++ b/tests/php/Storage/Sha1FileHashingServiceTest.php @@ -2,23 +2,12 @@ namespace SilverStripe\Assets\Tests\Storage; -use Exception; -use InvalidArgumentException; -use League\Flysystem\FileNotFoundException; use League\Flysystem\Filesystem; +use League\Flysystem\UnableToReadFile; use ReflectionMethod; -use Silverstripe\Assets\Dev\TestAssetStore; -use SilverStripe\Assets\File; -use SilverStripe\Assets\FilenameParsing\FileIDHelper; -use SilverStripe\Assets\FilenameParsing\HashFileIDHelper; -use SilverStripe\Assets\FilenameParsing\LegacyFileIDHelper; -use SilverStripe\Assets\FilenameParsing\NaturalFileIDHelper; -use SilverStripe\Assets\FilenameParsing\ParsedFileID; -use SilverStripe\Assets\Flysystem\FlysystemAssetStore; use SilverStripe\Assets\Storage\AssetStore; use SilverStripe\Assets\Storage\FileHashingService; use SilverStripe\Assets\Storage\Sha1FileHashingService; -use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\SapphireTest; use Symfony\Component\Cache\CacheItem; @@ -62,8 +51,8 @@ protected function setUp(): void protected function tearDown(): void { parent::tearDown(); - $this->publicFs->deleteDir('Sha1FileHashingServiceTest'); - $this->protectedFs->deleteDir('Sha1FileHashingServiceTest'); + $this->publicFs->deleteDirectory('Sha1FileHashingServiceTest'); + $this->protectedFs->deleteDirectory('Sha1FileHashingServiceTest'); } public function testComputeFromStream() @@ -103,7 +92,7 @@ public function testComputeFromFile() public function testComputeMissingFile() { - $this->expectException(FileNotFoundException::class); + $this->expectException(UnableToReadFile::class); $service = new Sha1FileHashingService(); $service->computeFromFile('missing-file.text', AssetStore::VISIBILITY_PROTECTED); } @@ -150,7 +139,7 @@ public function testComputeTouchFile() // Our timestamp is accruate to the second, we need to wait a bit to make sure our new timestamp won't be in // the same second as the old one. sleep(2); - $this->publicFs->update($this->fileID, $this->protectedContent); + $this->publicFs->write($this->fileID, $this->protectedContent); $this->assertEquals( $this->protectedHash, From 349508447b66591671b8930a25cd3fc1f78fb97e Mon Sep 17 00:00:00 2001 From: Sabina Talipova <87288324+sabina-talipova@users.noreply.github.com> Date: Thu, 8 Dec 2022 10:44:21 +1300 Subject: [PATCH 07/32] API Remove deprecated code (#530) --- .upgrade.yml | 9 - _config/asset.yml | 1 - src/Dev/FixFolderPermissionsHelper.php | 69 - src/Dev/Tasks/FileMigrationHelper.php | 597 --------- src/Dev/Tasks/FolderMigrationHelper.php | 186 --- .../Tasks/LegacyThumbnailMigrationHelper.php | 233 ---- .../Tasks/NormaliseAccessMigrationHelper.php | 683 ---------- src/Dev/Tasks/SecureAssetsMigrationHelper.php | 189 --- src/Dev/Tasks/TagsToShortcodeHelper.php | 405 ------ src/Dev/Tasks/TagsToShortcodeTask.php | 52 - src/Dev/Tasks/VersionedFilesMigrationTask.php | 42 - src/Dev/TestAssetStore.php | 40 +- src/Dev/VersionedFilesMigrator.php | 196 --- src/File.php | 23 - src/FileMigrationHelper.php | 11 - src/FilenameParsing/LegacyFileIDHelper.php | 254 ---- src/FilenameParsing/NaturalFileIDHelper.php | 3 +- src/Filesystem.php | 24 - src/Flysystem/FlysystemAssetStore.php | 216 ---- src/ImageManipulation.php | 13 +- src/Shortcodes/FileLinkTracking.php | 11 - .../php/Dev/Tasks/FileMigrationHelperTest.php | 476 ------- .../php/Dev/Tasks/FileMigrationHelperTest.yml | 71 -- .../FileMigrationHelperTest/Extension.php | 26 - .../Tasks/FixFolderPermissionsHelperTest.php | 30 - .../Tasks/FixFolderPermissionsHelperTest.yml | 11 - .../LegacyThumbnailMigrationHelperTest.php | 404 ------ .../LegacyThumbnailMigrationHelperTest.yml | 19 - .../NormaliseAccessMigrationHelperTest.php | 1110 ----------------- .../NormaliseAccessMigrationHelperTest.yml | 22 - ...eAccessMigrationHelperWithHashPathTest.php | 181 --- ...essMigrationHelperWithKeepArchivedTest.php | 89 -- .../Tasks/SS4CrazyFileMigrationHelperTest.php | 71 -- .../Dev/Tasks/SS4FileMigrationHelperTest.php | 315 ----- ...SS4KeepArchivedFileMigrationHelperTest.php | 43 - .../SS4LegacyFileMigrationHelperTest.php | 53 - .../Tasks/SecureAssetsMigrationHelperTest.php | 197 --- .../Tasks/SecureAssetsMigrationHelperTest.yml | 23 - tests/php/Dev/Tasks/Shortcode/HtmlObject.php | 17 - tests/php/Dev/Tasks/Shortcode/NoStage.php | 19 - tests/php/Dev/Tasks/Shortcode/PseudoPage.php | 21 - .../php/Dev/Tasks/Shortcode/SubHtmlObject.php | 15 - .../Dev/Tasks/TagsToShortcodeHelperTest.php | 543 -------- .../Dev/Tasks/TagsToShortcodeHelperTest.yml | 48 - tests/php/FileTest.php | 29 - .../FileIDHelperResolutionStrategyTest.php | 7 +- .../FilenameParsing/FileIDHelperTester.php | 23 - .../LegacyFileIDHelperTest.php | 249 ---- .../MigrationLegacyFileIDHelperTest.php | 191 --- tests/php/RedirectFileControllerTest.php | 20 +- tests/php/Storage/AssetStoreTest.php | 546 +------- .../Storage/AssetStoreTest/TestAssetStore.php | 14 - .../Storage/DefaultAssetNameGeneratorTest.php | 10 +- tests/php/VersionedFilesMigratorTest.php | 106 -- 54 files changed, 42 insertions(+), 8214 deletions(-) delete mode 100644 src/Dev/FixFolderPermissionsHelper.php delete mode 100644 src/Dev/Tasks/FileMigrationHelper.php delete mode 100644 src/Dev/Tasks/FolderMigrationHelper.php delete mode 100644 src/Dev/Tasks/LegacyThumbnailMigrationHelper.php delete mode 100644 src/Dev/Tasks/NormaliseAccessMigrationHelper.php delete mode 100644 src/Dev/Tasks/SecureAssetsMigrationHelper.php delete mode 100644 src/Dev/Tasks/TagsToShortcodeHelper.php delete mode 100644 src/Dev/Tasks/TagsToShortcodeTask.php delete mode 100644 src/Dev/Tasks/VersionedFilesMigrationTask.php delete mode 100644 src/Dev/VersionedFilesMigrator.php delete mode 100644 src/FileMigrationHelper.php delete mode 100644 src/FilenameParsing/LegacyFileIDHelper.php delete mode 100644 tests/php/Dev/Tasks/FileMigrationHelperTest.php delete mode 100644 tests/php/Dev/Tasks/FileMigrationHelperTest.yml delete mode 100644 tests/php/Dev/Tasks/FileMigrationHelperTest/Extension.php delete mode 100644 tests/php/Dev/Tasks/FixFolderPermissionsHelperTest.php delete mode 100644 tests/php/Dev/Tasks/FixFolderPermissionsHelperTest.yml delete mode 100644 tests/php/Dev/Tasks/LegacyThumbnailMigrationHelperTest.php delete mode 100644 tests/php/Dev/Tasks/LegacyThumbnailMigrationHelperTest.yml delete mode 100644 tests/php/Dev/Tasks/NormaliseAccessMigrationHelperTest.php delete mode 100644 tests/php/Dev/Tasks/NormaliseAccessMigrationHelperTest.yml delete mode 100644 tests/php/Dev/Tasks/NormaliseAccessMigrationHelperWithHashPathTest.php delete mode 100644 tests/php/Dev/Tasks/NormaliseAccessMigrationHelperWithKeepArchivedTest.php delete mode 100644 tests/php/Dev/Tasks/SS4CrazyFileMigrationHelperTest.php delete mode 100644 tests/php/Dev/Tasks/SS4FileMigrationHelperTest.php delete mode 100644 tests/php/Dev/Tasks/SS4KeepArchivedFileMigrationHelperTest.php delete mode 100644 tests/php/Dev/Tasks/SS4LegacyFileMigrationHelperTest.php delete mode 100644 tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.php delete mode 100644 tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.yml delete mode 100644 tests/php/Dev/Tasks/Shortcode/HtmlObject.php delete mode 100644 tests/php/Dev/Tasks/Shortcode/NoStage.php delete mode 100644 tests/php/Dev/Tasks/Shortcode/PseudoPage.php delete mode 100644 tests/php/Dev/Tasks/Shortcode/SubHtmlObject.php delete mode 100644 tests/php/Dev/Tasks/TagsToShortcodeHelperTest.php delete mode 100644 tests/php/Dev/Tasks/TagsToShortcodeHelperTest.yml delete mode 100644 tests/php/FilenameParsing/LegacyFileIDHelperTest.php delete mode 100644 tests/php/FilenameParsing/MigrationLegacyFileIDHelperTest.php delete mode 100644 tests/php/Storage/AssetStoreTest/TestAssetStore.php delete mode 100644 tests/php/VersionedFilesMigratorTest.php diff --git a/.upgrade.yml b/.upgrade.yml index fe5a6544..c4a99ed1 100644 --- a/.upgrade.yml +++ b/.upgrade.yml @@ -32,7 +32,6 @@ mappings: File: SilverStripe\Assets\File SS_FileFinder: SilverStripe\Assets\FileFinder SilverStripe\Assets\SS_FileFinder: SilverStripe\Assets\FileFinder - FileMigrationHelper: SilverStripe\Assets\FileMigrationHelper FileNameFilter: SilverStripe\Assets\FileNameFilter Filesystem: SilverStripe\Assets\Filesystem Folder: SilverStripe\Assets\Folder @@ -46,8 +45,6 @@ mappings: AssetControlExtensionTest_ArchivedObject: SilverStripe\Assets\Tests\AssetControlExtensionTest\ArchivedObject AssetManipulationListTest: SilverStripe\Assets\Tests\AssetManipulationListTest FileFinderTest: SilverStripe\Assets\Tests\FileFinderTest - FileMigrationHelperTest: SilverStripe\Assets\Tests\FileMigrationHelperTest - FileMigrationHelperTest_Extension: SilverStripe\Assets\Tests\FileMigrationHelperTest\Extension FileNameFilterTest: SilverStripe\Assets\Tests\FileNameFilterTest FileTest: SilverStripe\Assets\Tests\FileTest FileTest_MyCustomFile: SilverStripe\Assets\Tests\FileTest\MyCustomFile @@ -141,14 +138,8 @@ warnings: 'SilverStripe\Assets\Image::AssetLibraryThumbnail()': message: 'Renamed to CMSThumbnail()' replacement: 'CMSThumbnail' - 'SilverStripe\Assets\Flysystem\FlysystemAssetStore->parseFileID()': - message: 'Replaced with getDefaultFileIDHelper()->parseFileID()' 'SilverStripe\Assets\Flysystem\FlysystemAssetStore->getFileID()': message: 'Replace with getDefaultFileIDHelper()->buildFileID()' - 'SilverStripe\Assets\Flysystem\FlysystemAssetStore->getOriginalFilename()': - message: 'Replace with getDefaultFileIDHelper()->parseFileID()->getFilename()' - 'SilverStripe\Assets\Flysystem\FlysystemAssetStore->getVariant()': - message: 'Replace with getDefaultFileIDHelper()->parseFileID()->getVariant()' renameWarnings: - File - Image diff --git a/_config/asset.yml b/_config/asset.yml index e7a165b2..0977ed05 100644 --- a/_config/asset.yml +++ b/_config/asset.yml @@ -29,7 +29,6 @@ SilverStripe\Core\Injector\Injector: ResolutionFileIDHelpers: - '%$SilverStripe\Assets\FilenameParsing\HashFileIDHelper' - '%$SilverStripe\Assets\FilenameParsing\NaturalFileIDHelper' - - '%$SilverStripe\Assets\FilenameParsing\LegacyFileIDHelper' DefaultFileIDHelper: '%$SilverStripe\Assets\FilenameParsing\NaturalFileIDHelper' VersionedStage: Live # Define protected resolution strategy diff --git a/src/Dev/FixFolderPermissionsHelper.php b/src/Dev/FixFolderPermissionsHelper.php deleted file mode 100644 index 7b667f5e..00000000 --- a/src/Dev/FixFolderPermissionsHelper.php +++ /dev/null @@ -1,69 +0,0 @@ - '%$' . LoggerInterface::class, - ]; - - /** @var LoggerInterface */ - private $logger; - - public function __construct() - { - Deprecation::notice('1.12.0', 'Will be removed without equivalent functionality to replace it', Deprecation::SCOPE_CLASS); - $this->logger = new NullLogger(); - } - - /** - * @param LoggerInterface $logger - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - - return $this; - } - - /** - * @return int Returns the number of records updated. - */ - public function run() - { - SQLUpdate::create() - ->setTable('"' . File::singleton()->baseTable() . '"') - ->setAssignments(['"CanViewType"' => 'Inherit']) - ->setWhere([ - '"CanViewType" IS NULL', - '"ClassName"' => Folder::class - ]) - ->execute(); - - // This part won't work if run from the CLI, because Apache and the CLI don't share the same cache. - InheritedPermissionFlusher::flush(); - - return DB::affected_rows(); - } -} diff --git a/src/Dev/Tasks/FileMigrationHelper.php b/src/Dev/Tasks/FileMigrationHelper.php deleted file mode 100644 index 32842e1d..00000000 --- a/src/Dev/Tasks/FileMigrationHelper.php +++ /dev/null @@ -1,597 +0,0 @@ - '%$' . LoggerInterface::class . '.quiet', - ]; - - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var FlysystemAssetStore - */ - private $store; - - /** - * If a file fails to validate during migration, delete it. - * If set to false, the record will exist but will not be attached to any filesystem - * item anymore. - * - * @config - * @var bool - */ - private static $delete_invalid_files = true; - - public function __construct() - { - Deprecation::withNoReplacement(function () { - Deprecation::notice('1.12.0', 'Will be removed without equivalent functionality to replace it', Deprecation::SCOPE_CLASS); - }); - $this->logger = new NullLogger(); - } - - /** - * @param LoggerInterface $logger - * @return $this - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - - return $this; - } - - /** - * Perform migration - * - * @param string $base Absolute base path (parent of assets folder). Will default to PUBLIC_PATH - * @return int Number of files successfully migrated - */ - public function run($base = null) - { - $this->store = Injector::inst()->get(AssetStore::class); - if (!$this->store instanceof AssetStore || !method_exists($this->store, 'normalisePath')) { - throw new LogicException( - 'FileMigrationHelper: Can not run if the default asset store does not have a `normalisePath` method.' - ); - } - - if (empty($base)) { - $base = PUBLIC_PATH; - } - - // Set max time and memory limit - Environment::increaseTimeLimitTo(); - Environment::setMemoryLimitMax(-1); - Environment::increaseMemoryLimitTo(-1); - - /** @var FileHashingService $hasher */ - $hasher = Injector::inst()->get(FileHashingService::class); - $hasher::flush(); - - $this->logger->notice('Step 1/2: Migrate 3.x legacy files to new format'); - $ss3Count = $this->ss3Migration($base); - - $this->logger->notice('Step 2/2: Migrate files in <4.4 format to new format'); - - $ss4Count = 0; - if (class_exists(Versioned::class) && File::has_extension(Versioned::class)) { - Versioned::prepopulate_versionnumber_cache(File::class, Versioned::LIVE); - Versioned::prepopulate_versionnumber_cache(File::class, Versioned::DRAFT); - - $stages = [Versioned::LIVE => 'live', Versioned::DRAFT => 'draft']; - foreach ($stages as $stageId => $stageName) { - $this->logger->info(sprintf('Migrating files in the "%s" stage', $stageName)); - $count = Versioned::withVersionedMode(function () use ($stageId, $stageName) { - Versioned::set_stage($stageId); - return $this->normaliseAllFiles(sprintf('on the "%s" stage', $stageName)); - }); - if ($count) { - $this->logger->info(sprintf('Migrated %d files in the "%s" stage', $count, $stageName)); - } else { - $this->logger->info(sprintf( - 'No files required migrating in the "%s" stage', - $stageName - )); - } - - $ss4Count += $count; - } - } else { - $ss4Count = $this->normaliseAllFiles(''); - $this->logger->info(sprintf('Migrated %d files', $ss4Count)); - } - - return $ss3Count + $ss4Count; - } - - protected function ss3Migration($base) - { - // Check if the File dataobject has a "Filename" field. - // If not, cannot migrate - /** @skipUpgrade */ - if (!DB::get_schema()->hasField('File', 'Filename')) { - return 0; - } - - // Check if we have files to migrate - $totalCount = $this->getFileQuery()->count(); - if (!$totalCount) { - $this->logger->info('No files required migrating'); - return 0; - } - - $this->logger->debug(sprintf('Migrating %d files', $totalCount)); - - // Create a temporary SS3 Legacy File Resolution strategy for migrating SS3 Files - $initialStrategy = $this->store->getPublicResolutionStrategy(); - $ss3Strategy = $this->buildSS3MigrationStrategy($initialStrategy); - if (!$ss3Strategy) { - $this->logger->warning( - 'Skipping this step because the asset store is using an unsupported public file ' . - 'resolution strategy.' - ); - return 0; - } - $this->store->setPublicResolutionStrategy($ss3Strategy); - - // Force stage to draft - if (class_exists(Versioned::class) && File::has_extension(Versioned::class)) { - $originalState = Versioned::get_reading_mode(); - Versioned::set_stage(Versioned::DRAFT); - } - - // Set up things before going into the loop - $ss3Count = 0; - $originalState = null; - - // Loop over the files to migrate - try { - foreach ($this->getLegacyFileQuery() as $file) { - // Bypass the accessor and the filename from the column - $filename = $file->getField('Filename'); - $success = $this->migrateFile($base, $file, $filename); - if ($success) { - $ss3Count++; - } - } - } finally { - // Reset back to our initial state no matter what - if (class_exists(Versioned::class)) { - Versioned::set_reading_mode($originalState); - } - $this->store->setPublicResolutionStrategy($initialStrategy); - } - - // Show summary of results - if ($ss3Count > 0) { - $this->logger->info(sprintf('%d 3.x legacy files have been migrated.', $ss3Count)); - } else { - $this->logger->info(sprintf('No 3.x legacy files required migrating.', $ss3Count)); - } - - return $ss3Count; - } - - /** - * Construct a temporary SS3 File Resolution Strategy based off the provided initial strategy. - * If `$initialStrategy` is not suitable for a migration, we return null. - * We're overriding the helpers to avoid doing unnecessary checks. - * @param FileResolutionStrategy $initialStrategy - * @return int|FileIDHelperResolutionStrategy - */ - private function buildSS3MigrationStrategy(FileResolutionStrategy $initialStrategy) - { - // If the project is using a custom FileResolutionStrategy, we can't be confident that our migration won't - // break stuff, so let's bail - if (!$initialStrategy instanceof FileIDHelperResolutionStrategy) { - return null; - } - - // Let's make sure the initial strategy contains a LegacyFileIDHelper. If it doesn't, the owner of the project - // has explicitly disabled Legacy resolution, so there's no SS3 files to migrate - $foundLegacyHelper = false; - foreach ($initialStrategy->getResolutionFileIDHelpers() as $helper) { - if ($helper instanceof LegacyFileIDHelper) { - $foundLegacyHelper = true; - break; - } - } - if (!$foundLegacyHelper) { - return null; - } - - // Build the migration strategy - $ss3Strategy = FileIDHelperResolutionStrategy::create(); - $ss3Strategy->setDefaultFileIDHelper($initialStrategy->getDefaultFileIDHelper()); - $ss3Strategy->setResolutionFileIDHelpers([new LegacyFileIDHelper(false)]); - - return $ss3Strategy; - } - - /** - * Migrate a single file - * - * @param string $base Absolute base path (parent of assets folder) - * @param File $file - * @param string $legacyFilename - * @return bool True if this file is imported successfully - */ - protected function migrateFile($base, File $file, $legacyFilename) - { - // Make sure this legacy file actually exists - $path = $this->findNativeFile($base, $file, $legacyFilename); - if ($path === false) { - return false; - } - - // Ignore extensionless files - $extension = $file->getExtension(); - if ($extension === '') { - $this->handleInvalidFile($file, [['message' => 'Files without an extension can not be migrated']], $legacyFilename); - return false; - } - - // Fix file classname if it has a classname that's incompatible with its extension - if (!$this->validateFileClassname($file, $extension)) { - // We disable validation (if it is enabled) so that we are able to write a corrected - // classname, once that is changed we re-enable it for subsequent writes - $validationEnabled = DataObject::Config()->get('validation_enabled'); - if ($validationEnabled) { - DataObject::Config()->set('validation_enabled', false); - } - $destinationClass = $file->get_class_for_file_extension($extension); - $file = $file->newClassInstance($destinationClass); - $fileID = $file->write(); - if ($validationEnabled) { - DataObject::Config()->set('validation_enabled', true); - } - $file = File::get_by_id($fileID); - } - - // Remove invalid files - $validationResult = $file->validate(); - if (!$validationResult->isValid()) { - $this->handleInvalidFile($file, $validationResult->getMessages(), $legacyFilename); - return false; - } - - // Copy local file into this filesystem - $dbFilename = $file->generateFilename(); - $fsFilename = $path === true ? $this->stripAssetsDir($legacyFilename) : $this->stripAssetsDir($path, $base); - if ($path === true && $fsFilename == $dbFilename) { - $results = $this->store->normalisePath($dbFilename); - } else { - $results = $this->store->setFromLocalFile( - $base . DIRECTORY_SEPARATOR . ASSETS_DIR . DIRECTORY_SEPARATOR . $fsFilename, - $dbFilename, - null, - null, - AssetStore::VISIBILITY_PROTECTED - ); - $this->store->delete($fsFilename, $results['Hash']); - } - - // Move file if the APL changes filename value - $file->File->Filename = $results['Filename']; - $file->File->Hash = $results['Hash']; - $file->setField('Filename', null); - - // Save and publish - try { - if (class_exists(Versioned::class)) { - $file->writeToStage(Versioned::LIVE); - } else { - $file->write(); - } - } catch (ValidationException $e) { - if ($this->logger) { - $this->logger->error(sprintf( - "File %s could not be migrated due to an error. - This problem likely existed before the migration began. Error: %s", - $legacyFilename, - $e->getMessage() - )); - } - return false; - } - - if ($dbFilename == $file->getFilename()) { - $this->logger->info(sprintf('* SS3 file %s converted to SS4 format', $file->getFilename())); - } else { - $this->logger->warning(sprintf( - '* SS3 file %s converted to SS4 format but was renamed to %s', - $dbFilename, - $file->getFilename() - )); - } - - if (!empty($results['Operations'])) { - foreach ($results['Operations'] as $origin => $destination) { - $this->logger->debug(sprintf(' related file %s moved to %s', $origin, $destination)); - } - } - - return true; - } - - /** - * Look for a file by `$legacyFilename`. If an exact match is found return true. If no match is found return false. - * If a match using a different case is found use the path of that file. - * @param string $base Location of the assets folder, usually equals to ASSETS_PATH - * @param File $file - * @param string $legacyFilename SS3 filename prefix with ASSETS_DIR - * @return bool|string True if file is found as-is, false if file is missing or string to alternative file - */ - private function findNativeFile($base, File $file, $legacyFilename) - { - $path = $base . DIRECTORY_SEPARATOR . $legacyFilename; - if (file_exists($path ?? '')) { - return true; - } - - $strippedLegacyFilename = $this->stripAssetsDir($legacyFilename); - - // Try to find an alternative file - $legacyFilenameGlob = preg_replace_callback('/[a-z]/i', function ($matches) { - return sprintf('[%s%s]', strtolower($matches[0] ?? ''), strtoupper($matches[0] ?? '')); - }, $strippedLegacyFilename ?? ''); - - $files = glob($base . DIRECTORY_SEPARATOR . ASSETS_DIR . DIRECTORY_SEPARATOR . $legacyFilenameGlob); - - switch (sizeof($files ?? [])) { - case 0: - $this->logger->error(sprintf( - '%s could not be migrated because no matching file was found.', - $legacyFilename - )); - return false; - case 1: - $path = $files[0]; - break; - default: - // We found multiple files with same casing, life's just too dam complicated - $this->logger->error(sprintf( - '%s could not be migrated because no matching file was found. ' . - 'Other files with alternative casing exists: %s', - $legacyFilename, - implode(', ', $files) - )); - return false; - } - - // Make sure we do not have an unmigrated or migrated DB entry for our alternative file. - $strippedPath = $this->stripAssetsDir($path, $base); - $unmigratedFiles = $this->getFileQuery() - ->filter('Filename:case', ASSETS_DIR . '/' . $strippedPath) - ->exclude('ID', $file->ID); - $migratedFiles = File::get()->filter('FileFilename:case', $strippedPath) - ->exclude('ID', $file->ID); - - if ($unmigratedFiles->count() > 0 || $migratedFiles->count() > 0) { - $this->logger->error(sprintf( - '%s could not be migrated because no matching file was found.', - $legacyFilename - )); - return false; - } - - // We can use our alternative file - $this->logger->warning(sprintf( - '%s could not be found, but a file with an alternative casing was identified. %s will be used instead.', - $legacyFilename, - $strippedPath - )); - return $path; - } - - /** - * Given a path, remove the asset dir folder and the optional base path. - * @param $path - * @param string $base - * @return string - */ - private function stripAssetsDir($path, $base = '') - { - return preg_replace( - sprintf( - '#^%s%s%s?#', - $base ? $base . DIRECTORY_SEPARATOR : DIRECTORY_SEPARATOR . '?', - ASSETS_DIR, - DIRECTORY_SEPARATOR - ), - '', - $path ?? '' - ); - } - - /** - * Go through the list of files and make sure each one is at its default location - * @param string $stageString Complement of information to append to the confirmation message - * @param bool $skipIdenticalStages Whatever files that are already present on an other stage should be skipped - * @return int - */ - protected function normaliseAllFiles($stageString, $skipIdenticalStages = false) - { - $count = 0; - - $files = $this->chunk(File::get()->exclude('ClassName', [Folder::class, 'Folder'])); - - /** @var File $file */ - foreach ($files as $file) { - // There's no point doing those checks the live and draft file are the same - if ($skipIdenticalStages && !$file->stagesDiffer()) { - continue; - } - - if (!$this->store->exists($file->File->Filename, $file->File->Hash)) { - $this->logger->warning(sprintf( - ' Can not migrate %s / %s because it does not exist %s', - $file->File->Filename, - $file->File->Hash, - $stageString - )); - continue; - } - - $results = $this->store->normalise($file->File->Filename, $file->File->Hash); - if ($results && !empty($results['Operations'])) { - $this->logger->debug( - sprintf(' %s has been migrated %s', $file->getFilename(), $stageString) - ); - foreach ($results['Operations'] as $origin => $destination) { - $this->logger->debug(sprintf(' related file %s moved to %s', $origin, $destination)); - } - $count++; - } - } - - return $count; - } - - /** - * Check if a file's classname is compatible with it's extension - * - * @param File $file - * @param string $extension - * @return bool - */ - protected function validateFileClassname($file, $extension) - { - $destinationClass = $file->get_class_for_file_extension($extension); - return $file->ClassName === $destinationClass; - } - - /** - * Get list of File dataobjects to import - * - * @return DataList - */ - protected function getFileQuery() - { - $table = DataObject::singleton(File::class)->baseTable(); - // Select all records which have a Filename value, but not FileFilename. - /** @skipUpgrade */ - return File::get() - ->exclude('ClassName', [Folder::class, 'Folder']) - ->filter('FileFilename', ['', null]) - ->where(sprintf( - '"%s"."Filename" IS NOT NULL AND "%s"."Filename" != \'\'', - $table, - $table - )) // Non-orm field - ->alterDataQuery(function (DataQuery $query) use ($table) { - return $query->addSelectFromTable($table, ['Filename']); - }); - } - - protected function getLegacyFileQuery() - { - return $this->chunk($this->getFileQuery()); - } - - /** - * Split queries into smaller chunks to avoid using too much memory - * @param DataList $query - * @return Generator - */ - private function chunk(DataList $query) - { - $chunkSize = 100; - $greaterThanID = 0; - $query = $query->limit($chunkSize)->sort('ID'); - while ($chunk = $query->filter('ID:GreaterThan', $greaterThanID)) { - foreach ($chunk as $file) { - yield $file; - } - if ($chunk->count() == 0) { - break; - } - $greaterThanID = $file->ID; - } - } - - /** - * Get map of File IDs to legacy filenames - * - * @deprecated 1.4.0 Will be removed without equivalent functionality to replace it - * @return array - */ - protected function getFilenameArray() - { - Deprecation::notice('1.4.0', 'Will be removed without equivalent functionality to replace it'); - $table = DataObject::singleton(File::class)->baseTable(); - // Convert original query, ensuring the legacy "Filename" is included in the result - /** @skipUpgrade */ - return $this - ->getFileQuery() - ->dataQuery() - ->selectFromTable($table, ['ID', 'Filename']) - ->execute() - ->map(); // map ID to Filename - } - - /** - * @param File $file - * @param array $messages - * @param string $legacyFilename - * - * return void - */ - private function handleInvalidFile($file, $messages, $legacyFilename) - { - if ($this->config()->get('delete_invalid_files')) { - $file->delete(); - } - if ($this->logger) { - $logMessages = implode("\n\n", array_map(function ($msg) { - return $msg['message']; - }, $messages ?? [])); - $this->logger->warning( - sprintf( - " %s was not migrated because the file is not valid. More information: %s", - $legacyFilename, - $logMessages - ) - ); - } - } -} diff --git a/src/Dev/Tasks/FolderMigrationHelper.php b/src/Dev/Tasks/FolderMigrationHelper.php deleted file mode 100644 index 031f8fe0..00000000 --- a/src/Dev/Tasks/FolderMigrationHelper.php +++ /dev/null @@ -1,186 +0,0 @@ - '%$' . LoggerInterface::class . '.quiet', - ]; - - /** - * @var LoggerInterface - */ - private $logger; - - public function __construct() - { - Deprecation::notice('1.12.0', 'Will be removed without equivalent functionality to replace it', Deprecation::SCOPE_CLASS); - $this->logger = new NullLogger(); - } - - /** - * @param LoggerInterface $logger - * @return $this - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - - return $this; - } - - /** - * Perform migration - * - * @param string $base Absolute base path (parent of assets folder). Will default to PUBLIC_PATH - * @return int Number of files successfully migrated - */ - public function run() - { - if (empty($base)) { - $base = PUBLIC_PATH; - } - - // Set max time and memory limit - Environment::increaseTimeLimitTo(); - Environment::setMemoryLimitMax(-1); - Environment::increaseMemoryLimitTo(-1); - - $this->logger->notice('Migrate 3.x folder database records to new format'); - $ss3Count = $this->ss3Migration($base); - - return $ss3Count; - } - - protected function ss3Migration($base) - { - // Check if the File dataobject has a "Filename" field. - // If not, cannot migrate - /** @skipUpgrade */ - if (!DB::get_schema()->hasField('File', 'Filename')) { - return 0; - } - - if (!class_exists(Versioned::class) || !Folder::has_extension(Versioned::class)) { - $this->logger->info(sprintf('Folders are not versioned. Skipping migration.')); - return 0; - } - - // Check if we have folders to migrate - $totalCount = $this->getQuery()->count(); - if (!$totalCount) { - $this->logger->info('No folders required migrating'); - return 0; - } - - $this->logger->debug(sprintf('Migrating %d folders', $totalCount)); - - // Set up things before going into the loop - $processedCount = 0; - $successCount = 0; - $errorsCount = 0; - - // Loop over the files to migrate - foreach ($this->chunk($this->getQuery()) as $item) { - ++$processedCount; - - // Bypass the accessor and the filename from the column - $name = $item->getField('Filename'); - - $this->logger->info(sprintf('Migrating folder: %s', $name)); - - try { - $item->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); - ++$successCount; - } catch (\Exception $e) { - $this->logger->error(sprintf('Could not migrate folder: %s', $name), ['exception' => $e]); - ++$errorsCount; - } - } - - // Show summary of results - if ($processedCount > 0) { - $this->logger->info(sprintf('%d legacy folders have been processed.', $processedCount)); - $this->logger->info(sprintf('%d migrated successfully', $successCount)); - $this->logger->info(sprintf('%d errors', $errorsCount)); - } else { - $this->logger->info('No 3.x legacy folders required migration found.'); - } - - return $processedCount; - } - - /** - * Get list of File dataobjects to import - * - * @return DataList - */ - protected function getQuery() - { - $versionedExtension = Injector::inst()->get(Versioned::class); - - $schema = DataObject::getSchema(); - $baseDataClass = $schema->baseDataClass(Folder::class); - $baseDataTable = $schema->tableName($baseDataClass); - $liveDataTable = $versionedExtension->stageTable($baseDataTable, Versioned::LIVE); - - $query = Folder::get()->leftJoin( - $liveDataTable, - sprintf('"Live"."ID" = %s."ID"', Convert::symbol2sql($baseDataTable)), - 'Live' - )->alterDataQuery(static function (DataQuery $q) { - $q->selectField('"Filename"'); // used later for logging processed folders - $q->selectField('"Live"."ID"', 'LiveID'); // needed for having clause to work - $q->having(['"LiveID" IS NULL']); // filters all folders without a record in the live table - }); - - return $query; - } - - /** - * Split queries into smaller chunks to avoid using too much memory - * @param DataList $query - * @return Generator - */ - private function chunk(DataList $query) - { - $chunkSize = 100; - $greaterThanID = 0; - $query = $query->limit($chunkSize)->sort('ID'); - while ($chunk = $query->filter('ID:GreaterThan', $greaterThanID)) { - foreach ($chunk as $item) { - yield $item; - } - if ($chunk->count() == 0) { - break; - } - $greaterThanID = $item->ID; - } - } -} diff --git a/src/Dev/Tasks/LegacyThumbnailMigrationHelper.php b/src/Dev/Tasks/LegacyThumbnailMigrationHelper.php deleted file mode 100644 index 81b1c7d0..00000000 --- a/src/Dev/Tasks/LegacyThumbnailMigrationHelper.php +++ /dev/null @@ -1,233 +0,0 @@ - '%$' . LoggerInterface::class, - ]; - - /** @var LoggerInterface */ - private $logger; - - public function __construct() - { - Deprecation::notice('1.12.0', 'Will be removed without equivalent functionality to replace it', Deprecation::SCOPE_CLASS); - $this->logger = new NullLogger(); - } - - /** - * Perform migration - * - * @param FlysystemAssetStore $store - * @return array Map of old to new moved paths - */ - public function run(FlysystemAssetStore $store) - { - // Check if the File dataobject has a "Filename" field. - // If not, cannot migrate - /** @skipUpgrade */ - if (!DB::get_schema()->hasField('File', 'Filename')) { - return []; - } - - // Set max time and memory limit - Environment::increaseTimeLimitTo(); - Environment::setMemoryLimitMax(-1); - Environment::increaseMemoryLimitTo(-1); - - // Loop over all folders - $allMoved = []; - $originalState = null; - if (class_exists(Versioned::class)) { - $originalState = Versioned::get_reading_mode(); - Versioned::set_stage(Versioned::DRAFT); - } - - // Migrate root folder (not returned from query) - $moved = $this->migrateFolder($store, new Folder()); - if ($moved) { - $allMoved = array_merge($allMoved, $moved); - } - - // Migrate all nested folders - $folders = $this->getFolderQuery(); - foreach ($folders->dataQuery()->execute() as $folderData) { - // More memory efficient way to looping through large sets - /** @var Folder $folder */ - $folder = $folders->createDataObject($folderData); - $moved = $this->migrateFolder($store, $folder); - if ($moved) { - $allMoved = array_merge($allMoved, $moved); - } - } - if (class_exists(Versioned::class)) { - Versioned::set_reading_mode($originalState); - } - return $allMoved; - } - - /** - * @param LoggerInterface $logger - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - - return $this; - } - - /** - * Migrate a folder - * - * @param FlysystemAssetStore $store - * @param Folder $folder - * @param string $legacyFilename - * @return array Map of old to new file paths (relative to store root) - */ - protected function migrateFolder(FlysystemAssetStore $store, Folder $folder) - { - $moved = []; - - // Get normalised store relative path - $folderPath = preg_replace('#^/#', '', $folder->getFilename() ?? ''); - $resampledFolderPath = $folderPath . '_resampled'; - - // Legacy thumbnails couldn't have been stored in a protected filesystem - /** @var Filesystem $filesystem */ - $filesystem = $store->getPublicFilesystem(); - - if (!$filesystem->has($resampledFolderPath)) { - return $moved; - } - - $failNewerVariant = false; - $legacyFileIDParser = new LegacyFileIDHelper($failNewerVariant); - $naturalFileIDParser = new NaturalFileIDHelper(); - - $foundError = false; - // Recurse through folder - foreach ($filesystem->listContents($resampledFolderPath, true)->toArray() as $fileInfo) { - if ($fileInfo['type'] !== 'file') { - continue; - } - - $oldResampledPath = $fileInfo['path']; - - $parsedFileID = $legacyFileIDParser->parseFileID($oldResampledPath); - - // If we can't parse the fileID, let's bail on this file and print out an error - if (!$parsedFileID) { - $foundError = true; - $this->logger->error('Could not find valid variants in ' . $oldResampledPath); - continue; - } - - // Replicate new variant format. - // We're always placing these files in the public filesystem, *without* a content hash path. - // This means you need to run the optional migration task introduced in 4.4, - // which moves public files out of content hash folders. - // Image->manipulate() is in charge of creating the full file name (incl. variant), - // and assumes the manipulation is run on an existing original file, so we can't use it here. - // Any AssetStore-level filesystem operations (like move()) suffer from the same limitation, - // so we need to drop down to path based filesystem renames. - $newResampledPath = $naturalFileIDParser->buildFileID($parsedFileID); - - // Don't overwrite existing files in the new location, - // they might have been generated based on newer file contents - if ($filesystem->has($newResampledPath)) { - $filesystem->delete($oldResampledPath); - continue; - } - - $filesystem->move($oldResampledPath, $newResampledPath); - - $this->logger->info(sprintf('Moved legacy thumbnail %s to %s', $oldResampledPath, $newResampledPath)); - - $moved[$oldResampledPath] = $newResampledPath; - } - - // Remove folder and any subfolders. If one or more thumbnails didn't - // get migrated leave the folder where it is. - if (!$foundError) { - $files = array_filter( - $filesystem->listContents($resampledFolderPath, true)->toArray() ?? [], - function ($file) { - return $file['type'] === 'file'; - } - ); - if (empty($files)) { - $filesystem->deleteDirectory($resampledFolderPath); - } else { - // This should not be possible. If it is, then there's probably a bug. - $this->logger->error(sprintf( - 'Could not remove folder %s because it still contains files. Please submit a bug report at %s.', - $oldResampledPath, - 'https://github.com/silverstripe/silverstripe-assets/issues/new' - )); - } - } - - return $moved; - } - - /** - * Get list of Folder dataobjects to inspect for - * - * @return \SilverStripe\ORM\DataList - */ - protected function getFolderQuery() - { - $table = DataObject::singleton(File::class)->baseTable(); - // Select all records which have a Filename value, but not FileFilename. - /** @skipUpgrade */ - return File::get() - ->filter('ClassName', [Folder::class, 'Folder']) - ->filter('FileFilename', ['', null]) - ->alterDataQuery(function (DataQuery $query) use ($table) { - return $query->addSelectFromTable($table, ['Filename']); - }); - } -} diff --git a/src/Dev/Tasks/NormaliseAccessMigrationHelper.php b/src/Dev/Tasks/NormaliseAccessMigrationHelper.php deleted file mode 100644 index 2e3cc40c..00000000 --- a/src/Dev/Tasks/NormaliseAccessMigrationHelper.php +++ /dev/null @@ -1,683 +0,0 @@ - '%$' . LoggerInterface::class . '.quiet', - ]; - - /** - * @var LoggerInterface - */ - private $logger; - - /** - * List of error messages - * @var string[] - */ - private $errors = []; - - - /** - * @var FlysystemAssetStore - */ - private $store; - - /** - * Initial value of `keep_empty_dirs` on asset store. This value is restored at the end of the task. - * @var bool|null - */ - private $initial_keep_empty_dirs; - - /** - * List of folders to analyse at the end of the task to see if they can be deleted. - * @var string[] - */ - private $public_folders_to_truncate = []; - - /** - * List of cached live versions of files by ID. Methods design to loop over a chunk of result set. This cache can - * be pre-filleds cache to avoid fetching individual Live versions. - * @var File[] - */ - private $cached_live_files = []; - - - /** - * Prefix to remove from URL when truncating folders. Only used for testing. - * @var string - */ - private $basePath = ''; - - private $folderClasses; - - /** - * @param string $base Prefix for URLs. Only used for unit tests. - */ - public function __construct($base = '') - { - Deprecation::withNoReplacement(function () { - Deprecation::notice('1.12.0', 'Will be removed without equivalent functionality to replace it', Deprecation::SCOPE_CLASS); - }); - $this->logger = new NullLogger(); - if ($base) { - $this->basePath = $base; - } - $this->folderClasses = ClassInfo::subclassesFor(Folder::class, true); - } - - /** - * @param LoggerInterface $logger - * @return $this - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - - return $this; - } - - private function info($message) - { - $this->logger && $this->logger->info($message); - } - - private function notice($message) - { - $this->logger && $this->logger->notice($message); - } - - private function error($message) - { - $this->logger && $this->logger->error($message); - } - - private function debug($message) - { - $this->logger && $this->logger->debug($message); - } - - private function warning($message) - { - $this->logger && $this->logger->warning($message); - } - - /** - * Perform migration - * - * @return int[] Number of files successfully migrated - */ - public function run() - { - // Set max time and memory limit - Environment::increaseTimeLimitTo(); - Environment::setMemoryLimitMax(-1); - Environment::increaseMemoryLimitTo(-1); - $this->clearPermissionCheckerCache(); - - $this->errors = []; - - /** @var FlysystemAssetStore $store */ - $this->store = Injector::inst()->get(AssetStore::class); - - $step = 1; - $totalStep = 4; - - // Step 1 - Finding files - $this->notice(sprintf('Step %d/%d: Finding files with bad access permission', $step, $totalStep)); - $badFiles = $this->findBadFiles(); - - if (empty($badFiles)) { - if (empty($this->errors)) { - $this->notice('All your files have the correct access permission.'); - } else { - $this->warning(sprintf( - 'Could not find any files with incorrect access permission, but %d errors were found.', - sizeof($this->errors ?? []) - )); - } - - return [ - 'total' => 0, - 'success' => 0, - 'fail' => sizeof($this->errors ?? []) - ]; - } - - $this->notice(sprintf('%d files with bad access permission have been found.', sizeof($badFiles ?? []))); - - $step++; - - // Step 2 - Tweak asset store setting so we don't try deleting empty folders while moving files around - $this->notice(sprintf('Step %d/%d: Enabling "keep empty dirs" flag', $step, $totalStep)); - $this->disableKeepEmptyDirs(); - - $step++; - - // Step 3 - Move files to their correct spots - $this->notice(sprintf('Step %d/%d: Correct files with bad access permission', $step, $totalStep)); - $badFileIDs = array_keys($badFiles ?? []); - unset($badFiles); - - $success = 0; - $fail = 0; - $count = sizeof($badFileIDs ?? []); - foreach ($this->loopBadFiles($badFileIDs) as $file) { - try { - if ($this->fix($file)) { - $success++; - } - } catch (Exception $ex) { - $this->error($ex->getMessage()); - $this->errors[] = $ex->getMessage(); - } - } - - $step++; - - // Step 4 - Delete empty folders and re-enable normal logic to delete empty folders - $this->notice(sprintf('Step %d/%d: Clean up', $step, $totalStep)); - $this->cleanup(); - - // Print out some summary info - if ($this->errors) { - $this->warning(sprintf('Completed with %d errors', sizeof($this->errors ?? []))); - foreach ($this->errors as $error) { - $this->warning($error); - } - } else { - $this->notice('All files with bad access permission have been fixed'); - } - - return [ - 'total' => $count, - 'success' => $success, - 'fail' => sizeof($this->errors ?? []) - ]; - } - - /** - * Need to clear the Inherited Permission cache for files to make sure we don't get stale permissions. - */ - private function clearPermissionCheckerCache() - { - $permissionChecker = File::singleton()->getPermissionChecker(); - if (method_exists($permissionChecker, 'clearCache')) { - $permissionChecker->clearCache(); - } - if (method_exists($permissionChecker, 'flushMemberCache')) { - $permissionChecker->flushMemberCache(); - } - } - - /** - * Stash the initial value of `keep_empty_dirs` on the current asset store - */ - private function disableKeepEmptyDirs() - { - $this->public_folders_to_truncate = []; - $store = $this->store; - - if ($store instanceof FlysystemAssetStore) { - $this->initial_keep_empty_dirs = $store::config()->get('keep_empty_dirs'); - $store::config()->set('keep_empty_dirs', true); - $this->notice($this->initial_keep_empty_dirs ? - '`keep_empty_dirs` on AssetStore is already enabled' : - 'Enabling `keep_empty_dirs` on AssetStore'); - } else { - $this->notice('AssetStore does not support keep_empty_dirs. Skipping.'); - } - } - - /** - * Delete the empty folders that would have normally been deleted when moving files if `keep_empty_dirs` had - * been disabled and restore the initial `keep_empty_dirs` on the asset store. - */ - private function cleanup() - { - // If the asset store wasn't disabled when we started, there's nothing to do. - if ($this->initial_keep_empty_dirs !== false) { - $this->notice('AssetStore\'s keep_empty_dirs was not changed. Skipping.'); - return; - } - - $fs = $this->store->getPublicFilesystem(); - $truncatedPaths = []; - $this->notice('Truncating empty folders'); - - // Sort list of folders so that the deeper ones are processed first - usort($this->public_folders_to_truncate, function ($a, $b) { - $aCrumbs = explode('/', $a ?? ''); - $bCrumbs = explode('/', $b ?? ''); - if (sizeof($aCrumbs ?? []) > sizeof($bCrumbs ?? [])) { - return -1; - } - if (sizeof($aCrumbs ?? []) < sizeof($bCrumbs ?? [])) { - return 1; - } - return 0; - }); - - foreach ($this->public_folders_to_truncate as $path) { - if ($this->canBeTruncated($path, $fs)) { - $this->info(sprintf('Deleting empty folder %s', $path)); - $fs->deleteDirectory($path); - $truncatedPaths[] = $path; - } - } - - foreach ($truncatedPaths as $path) { - // For each folder that we've deleted, recursively check if we can now delete its parent. - $this->recursiveTruncate(dirname($path ?? ''), $fs); - } - - // Restore initial config - $store = $this->store; - $store::config()->set('keep_empty_dirs', false); - } - - /** - * Check if the provided folder can be deleted on this Filesystem - * @param string $path - * @param FilesystemOperator $fs - * @return bool - */ - private function canBeTruncated($path, FilesystemOperator $fs) - { - if (!$fs->directoryExists($path)) { - // The folder doesn't exists - return false; - } - - $contents = $fs->listContents($path, true)->toArray(); - - foreach ($contents as $content) { - if ($content['type'] !== 'dir') { - // if there's a file in the folder, we can't delete it - return false; - } - } - - // Lookup into each subfolder to see if they contain files. - foreach ($contents as $content) { - if (!$this->canBeTruncated($content['path'], $fs)) { - // At least one sub directory contains files - return false; - } - } - - return true; - } - - /** - * Delete this folder if it doesn't contain any files and parent folders if they don't contain any files either. - * @param string $path - * @param FilesystemOperator $fs - */ - private function recursiveTruncate($path, FilesystemOperator $fs) - { - if ($path && ltrim($path ?? '', '.') && empty($fs->listContents($path)->toArray()) - ) { - $this->info(sprintf('Deleting empty folder %s', $path)); - $fs->deleteDirectory($path); - $this->recursiveTruncate(dirname($path ?? ''), $fs); - } - } - - /** - * Find the parent folder for this file and add it to the list of folders to check for possible deletion at the - * end of the task. - * @param File $file - */ - private function markFolderForTruncating(File $file) - { - if ($this->initial_keep_empty_dirs !== false) { - return; - } - - $url = $file->getSourceURL(false); - - if ($this->basePath && strpos($url ?? '', $this->basePath ?? '') === 0) { - // This bit is only relevant for unit test because our URL will be prefixied with the TestAssetStore path - $url = substr($url ?? '', strlen($this->basePath ?? '')); - } - - $url = trim($url ?? '', '/'); - - if (strpos($url ?? '', ASSETS_DIR . '/') === 0) { - // Remove the assets prefix - $url = substr($url ?? '', strlen(ASSETS_DIR . '/')); - } - - $folderPath = trim(dirname($url ?? ''), '/'); - - - if (!in_array($folderPath, $this->public_folders_to_truncate ?? [])) { - $this->public_folders_to_truncate[] = $folderPath; - } - } - - /** - * Loop through all the files and find the ones that aren't stored in the correct store. - * - * Returns an array of bit masks with the ID of the file has the key. - * @return array - */ - public function findBadFiles() - { - $operations = []; - foreach ($this->loopFiles() as $file) { - try { - $ops = $this->needToMove($file); - if ($ops) { - $operations[$file->ID] = $ops; - } - } catch (Exception $ex) { - $this->error($ex->getMessage()); - $this->errors[] = $ex->getMessage(); - } - } - - foreach ($operations as &$ops) { - $ops = array_filter($ops ?? [], function ($storeToMove) { - // We only keep operation that involvs protecting files for now. - return $storeToMove === AssetStore::VISIBILITY_PROTECTED; - }); - } - - return array_filter($operations ?? [], function ($ops) { - return !empty($ops); - }); - } - - /** - * Loop over the files in chunks to save memory. - * @return Generator|File[] - */ - private function loopFiles() - { - $limit = 100; - $offset = 0; - $count = 0; - - do { - $count = 0; - $files = File::get() - ->limit($limit, $offset) - ->sort('ID') - ->exclude('ClassName', $this->folderClasses); - - $IDs = $files->getIDList(); - $this->preCacheLiveFiles($IDs); - - foreach ($files as $file) { - yield $file; - $count++; - } - $offset += $limit; - } while ($count === $limit); - } - - /** - * Make sure all versions of the povided file are stored in the correct asset store. - * @param File $file - * @return bool Whether the fix was completed succesfully - */ - public function fix(File $file) - { - $success = true; - $actions = $this->needToMove($file); - - // Make sure restricted live files are protected - if (isset($actions[Versioned::LIVE]) && $actions[Versioned::LIVE] === AssetStore::VISIBILITY_PROTECTED) { - $liveFile = $this->getLive($file); - $this->markFolderForTruncating($liveFile); - if ($liveFile) { - $liveFile->protectFile(); - $this->info(sprintf('Protected live file %s', $liveFile->getFilename())); - } else { - $message = sprintf('Could not protected live file %s', $file->getFilename()); - $success = false; - $this->error($message); - $this->errors[] = $message; - } - } - - // Make sure draft files are protected - if (isset($actions[Versioned::DRAFT]) && $actions[Versioned::DRAFT] === AssetStore::VISIBILITY_PROTECTED) { - $this->markFolderForTruncating($file); - $file->protectFile(); - $this->info(sprintf('Protected draft file %s', $file->getFilename())); - } - - // Make sure unrestricted live files are public - if (isset($actions[Versioned::LIVE]) && $actions[Versioned::LIVE] === AssetStore::VISIBILITY_PUBLIC) { - $liveFile = $this->getLive($file); - if ($liveFile) { - $liveFile->publishFile(); - $this->info(sprintf('Published live file %s', $liveFile->getFilename())); - } else { - $message = sprintf('Could not published live file %s', $file->getFilename()); - $this->error($message); - $success = false; - $this->errors[] = $message; - } - } - - return $success; - } - - /** - * Return the live version of the provided file. - * @param File $file - * @return File|null - */ - private function getLive(File $file) - { - $ID = $file->ID; - - // If we've pre-warmed the cache get the value from there - if (isset($this->cached_live_file[$ID])) { - return $this->cached_live_file[$ID]; - } - - if ($file->isLiveVersion()) { - return $file; - } - - $liveVersion = Versioned::get_versionnumber_by_stage(File::class, Versioned::LIVE, $ID); - - return $liveVersion ? - Versioned::get_version(File::class, $ID, $liveVersion) : - null ; - } - - /** - * Pre warm the live version cache by loading the live version of provided File IDs. - * @param int[] $IDs - */ - private function preCacheLiveFiles(array $IDs) - { - if (empty($IDs)) { - return; - } - - $cached = []; - foreach ($IDs as $ID) { - $cached[$ID] = null; - } - - $liveFiles = Versioned - ::get_by_stage(File::class, Versioned::LIVE) - ->exclude('ClassName', $this->folderClasses) - ->filter('ID', $IDs); - - - foreach ($liveFiles as $file) { - $cached[$file->ID] = $file; - } - - $this->cached_live_file = $cached; - } - - /** - * Determine if the versions of the provided file are stored in the correct asset store. - * @param File $draftFile - * @throws InvalidArgumentException When the provided `$draftFile` is invalid - * @throws LogicException When there's some unexpected condition with the file - * @return int Bitmask for the operations to perform on the file - */ - public function needToMove(File $draftFile) - { - $this->validateInputFile($draftFile); - - /** @var File $liveFile */ - $liveFile = $this->getLive($draftFile); - - // We only need to check permission on the draft file if both those conditions are true: - // * live version is not the latest draft - // * the draft filename or file hash have changed - $checkDraftFile = !$liveFile || ( - $liveFile->Version !== $draftFile->Version && - ( - $draftFile->getFilename() !== $liveFile->getFilename() || - $draftFile->getHash() !== $liveFile->getHash() - ) - ); - - $moveOperations = []; - - if ($liveFile) { - $moveOperations[Versioned::LIVE] = $this->needToMoveVersion($liveFile); - } - - if ($checkDraftFile) { - $moveOperations[Versioned::DRAFT] = $this->needToMoveVersion($draftFile); - } - - return array_filter($moveOperations ?? []); - } - - /** - * Check if the specific file version provided needs to be move to a different store. - * @param File $file - * @return string|false - */ - private function needToMoveVersion(File $file) - { - $visibility = $file->getVisibility(); - $this->validateVisibility($file, $visibility); - $canView = Member::actAs(null, function () use ($file) { - return $file->canView(); - }); - - if (!$canView && $visibility === AssetStore::VISIBILITY_PUBLIC) { - // File should be protected but is on the public store - return AssetStore::VISIBILITY_PROTECTED; - } - - if ($canView && $visibility === AssetStore::VISIBILITY_PROTECTED) { - // File should be public but is on the protected store - return AssetStore::VISIBILITY_PUBLIC; - } - - return false; - } - - /** - * Make sure the provided file is suitable for process by needToMove. - * - * @param File $file - * @throws InvalidArgumentException - */ - private function validateInputFile(File $file) - { - if (in_array($file->ClassName, $this->folderClasses ?? [])) { - throw new InvalidArgumentException(sprintf( - '%s::%s(): Provided File can not be a Folder', - __CLASS__, - __METHOD__ - )); - } - } - - /** - * Make sure the provided visibility is a valid visibility string from AssetStore. - * @param File $file - * @param string $visibility - * @throws LogicException - */ - private function validateVisibility(File $file, $visibility) - { - $validVisibilities = [AssetStore::VISIBILITY_PROTECTED, AssetStore::VISIBILITY_PUBLIC]; - if (!in_array($visibility, $validVisibilities ?? [])) { - throw new LogicException(sprintf( - '%s::%s(): File %s visibility of "%s" is invalid', - __CLASS__, - __METHOD__, - $file->getFilename(), - $visibility - )); - } - } - - /** - * Fetch the list of provided files in chunck from the DB - * @param int[] $IDs - * @return \Generator|File[] - */ - private function loopBadFiles(array $IDs) - { - $limit = 100; - $yieldCount = 0; - $total = sizeof($IDs ?? []); - - $chunks = array_chunk($IDs ?? [], $limit ?? 0); - unset($IDs); - - foreach ($chunks as $chunk) { - $this->preCacheLiveFiles($chunk); - - foreach (File::get()->filter('ID', $chunk) as $file) { - yield $file; - } - $yieldCount += sizeof($chunk ?? []); - $this->notice(sprintf('Processed %d files out of %d', $yieldCount, $total)); - } - } -} diff --git a/src/Dev/Tasks/SecureAssetsMigrationHelper.php b/src/Dev/Tasks/SecureAssetsMigrationHelper.php deleted file mode 100644 index d17115ae..00000000 --- a/src/Dev/Tasks/SecureAssetsMigrationHelper.php +++ /dev/null @@ -1,189 +0,0 @@ - '%$' . LoggerInterface::class . '.quiet', - ]; - - /** - * @var LoggerInterface - */ - private $logger; - - public function __construct() - { - Deprecation::notice('1.12.0', 'Will be removed without equivalent functionality to replace it', Deprecation::SCOPE_CLASS); - - $this->logger = new NullLogger(); - - $this->htaccessRegexes = [ - '#RewriteEngine On#', - '#RewriteBase .*#', // allow any base folder - '#' . preg_quote('RewriteCond %{REQUEST_URI} ^(.*)$') . '#', - '#RewriteRule .*' . preg_quote('main.php?url=%1 [QSA]') . '#' // allow any framework base path - ]; - } - - /** - * Perform migration - * - * @param FlysystemAssetStore $store - * @return array Folders which needed migration - */ - public function run(FlysystemAssetStore $store) - { - $migrated = []; - - // There's no way .htaccess files could've been created by silverstripe/secureassets - // in a protected filesystem (didn't exist in 3.x) - $filesystem = $store->getPublicFilesystem(); - - // The presence of secured folders can either come - // from a freshly migrated 3.x database with silverstripe/secureassets, - // or from an already used 4.x database with built-in asset protections. - // Because the module itself has been removed in 4.x installs, - // we can no longer tell the difference between those cases. - $fileTable = DataObject::getSchema()->baseDataTable(File::class); - $securedFolders = SQLSelect::create() - ->setFrom("\"$fileTable\"") - ->setSelect([ - '"ID"', - '"FileFilename"', - ]) - ->addWhere([ - '"ClassName" = ?' => Folder::class, - // We don't need to check 'Inherited' permissions, - // since Apache applies parent .htaccess and the module doesn't create them in this case. - // See SecureFileExtension->needsAccessFile() - '"CanViewType" IN(?,?)' => ['LoggedInUsers', 'OnlyTheseUsers'] - ]); - - if (!$securedFolders->count()) { - $this->logger->info('No need for secure files migration'); - return []; - } - - foreach ($securedFolders->execute()->map() as $id => $path) { - /** @var Folder $folder */ - if (!$folder = Folder::get()->byID($id)) { - $this->logger->warning(sprintf('No Folder record found for ID %d. Skipping', $id)); - continue; - } - - $migratedPath = $this->migrateFolder($filesystem, $folder->getFilename()); - - if ($migratedPath) { - $migrated[] = $migratedPath; - } - } - - return $migrated; - } - - /** - * @param LoggerInterface $logger - * @return $this - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - - return $this; - } - - /** - * A "somewhat exact" match on file contents, - * to avoid deleting any customised files. - * Checks each line in a file, with some leeway - * for different base folders which are dynamically generated - * based on the context of a particular environment. - * - * @param string $content - * @return bool - */ - public function htaccessMatch($content) - { - $regexes = $this->htaccessRegexes; - $lines = explode("\n", $content ?? ''); - - if (count($lines ?? []) != count($regexes ?? [])) { - return false; - } - - foreach ($lines as $i => $line) { - if (!preg_match($regexes[$i] ?? '', $line ?? '')) { - return false; - } - } - - return true; - } - - /** - * @param Filesystem $filesystem - * @param string $path - * return string|null The path of the migrated file (if successful) - */ - protected function migrateFolder(Filesystem $filesystem, $path) - { - $htaccessPath = $path . '.htaccess'; - - if (!$filesystem->has($htaccessPath)) { - return null; - } - - $content = $filesystem->read($htaccessPath); - - if ($this->htaccessMatch($content)) { - $filesystem->delete($htaccessPath); - $this->logger->debug(sprintf( - 'Removed obsolete secureassets .htaccess at %s', - $path - )); - return $htaccessPath; - } else { - $this->logger->warning(sprintf( - 'Skipped non-standard htaccess file (not generated by secureassets?): %s', - $path - )); - return null; - } - } -} diff --git a/src/Dev/Tasks/TagsToShortcodeHelper.php b/src/Dev/Tasks/TagsToShortcodeHelper.php deleted file mode 100644 index abc07dcc..00000000 --- a/src/Dev/Tasks/TagsToShortcodeHelper.php +++ /dev/null @@ -1,405 +0,0 @@ - 'image', - 'a' => 'file_link' - ]; - - const VALID_ATTRIBUTES = [ - 'src', - 'href' - ]; - - /** @var string */ - private $validTagsPattern; - - /** @var string */ - private $validAttributesPattern; - - /** @var FlysystemAssetStore */ - private $flysystemAssetStore; - - /** @var string */ - private $baseClass; - - /** @var bool */ - private $includeBaseClass; - - private static $dependencies = [ - 'logger' => '%$' . LoggerInterface::class, - ]; - - /** @var LoggerInterface|null */ - private $logger; - - /** - * @param LoggerInterface $logger - */ - public function setLogger(LoggerInterface $logger) - { - $this->logger = $logger; - } - - /** - * TagsToShortcodeHelper constructor. - * @param string $baseClass The base class that will be used to look up HTMLText fields - * @param bool $includeBaseClass Whether to include the base class' HTMLText fields or not - */ - public function __construct($baseClass = null, $includeBaseClass = false) - { - Deprecation::notice('1.12.0', 'Will be removed without equivalent functionality to replace it', Deprecation::SCOPE_CLASS); - $flysystemAssetStore = singleton(AssetStore::class); - if (!($flysystemAssetStore instanceof FlysystemAssetStore)) { - throw new InvalidArgumentException("FlysystemAssetStore missing"); - } - $this->flysystemAssetStore = $flysystemAssetStore; - - $this->baseClass = $baseClass ?: DataObject::class; - $this->includeBaseClass = $includeBaseClass; - - $this->validTagsPattern = implode('|', array_keys(static::VALID_TAGS ?? [])); - $this->validAttributesPattern = implode('|', static::VALID_ATTRIBUTES); - } - - /** - * @throws \ReflectionException - */ - public function run() - { - Environment::increaseTimeLimitTo(); - - $classes = $this->getFieldMap($this->baseClass, $this->includeBaseClass, [ - 'HTMLText', - 'HTMLVarchar' - ]); - - $tableList = DB::table_list(); - - foreach ($classes as $class => $tables) { - /** @var DataObject $singleton */ - $singleton = singleton($class); - $hasVersions = - class_exists(Versioned::class) && - $singleton->hasExtension(Versioned::class) && - $singleton->hasStages(); - - foreach ($tables as $table => $fields) { - foreach ($fields as $field) { - - /** @var DBField $dbField */ - $dbField = DataObject::singleton($class)->dbObject($field); - if ($dbField && - $dbField->hasMethod('getProcessShortcodes') && - !$dbField->getProcessShortcodes()) { - continue; - } - - if (!isset($tableList[strtolower($table)])) { - // When running unit test some tables won't be created. We'll just skip those. - continue; - } - - - // Update table - $this->updateTable($table, $field); - - // Update live - if ($hasVersions) { - $this->updateTable($table.'_Live', $field); - } - } - } - } - } - - private function updateTable($table, $field) - { - $sqlSelect = SQLSelect::create(['"ID"', "\"$field\""], "\"$table\""); - $whereAnys = []; - foreach (array_keys(static::VALID_TAGS ?? []) as $tag) { - $whereAnys[]= "\"$table\".\"$field\" LIKE '%<$tag%'"; - $whereAnys[]= "\"$table\".\"$field\" LIKE '%[$tag%'"; - } - $sqlSelect->addWhereAny($whereAnys); - $records = $sqlSelect->execute(); - $this->rewriteFieldForRecords($records, $table, $field); - } - - /** - * Takes a set of query results and updates image urls within a page's content. - * @param Query $records - * @param string $updateTable - * @param string $field - */ - private function rewriteFieldForRecords(Query $records, $updateTable, $field) - { - foreach ($records as $row) { - $content = $row[$field]; - $newContent = $this->getNewContent($content); - if ($content == $newContent) { - continue; - } - - $updateSQL = SQLUpdate::create("\"$updateTable\"")->addWhere(['"ID"' => $row['ID']]); - $updateSQL->addAssignments(["\"$field\"" => $newContent]); - $updateSQL->execute(); - if ($this->logger) { - $this->logger->info("Updated record ID {$row['ID']} on table $updateTable"); - } - } - } - - /** - * @param string $content - * @return string - */ - public function getNewContent($content) - { - $tags = $this->getTagsInContent($content); - foreach ($tags as $tag) { - if ($newTag = $this->getNewTag($tag)) { - $content = str_replace($tag ?? '', $newTag ?? '', $content ?? ''); - } - } - - return $content; - } - - /** - * Get all tags within some page content and return them as an array. - * @param string $content The page content - * @return array An array of tags found - */ - private function getTagsInContent($content) - { - $resultTags = []; - - $regex = '/<('.$this->validTagsPattern.')\s[^>]*?('.$this->validAttributesPattern.')\s*=.*?>/i'; - preg_match_all($regex ?? '', $content ?? '', $matches, PREG_SET_ORDER); - if ($matches) { - foreach ($matches as $match) { - $resultTags []= $match[0]; - } - } - - return $resultTags; - } - - /** - * @param string $tag - * @return array - */ - private function getTagTuple($tag) - { - $pattern = sprintf( - '/.*(?:<|\[)(?%s).*(?%s)=(?:"|\')(?[^"]*)(?:"|\')/i', - $this->validTagsPattern, - $this->validAttributesPattern - ); - preg_match($pattern ?? '', $tag ?? '', $matches); - return $matches; - } - - /** - * @param string $src - * @return null|ParsedFileID - * @throws \League\Flysystem\Exception - */ - private function getParsedFileIDFromSrc($src) - { - $fileIDHelperResolutionStrategy = FileIDHelperResolutionStrategy::create(); - $fileIDHelperResolutionStrategy->setResolutionFileIDHelpers([ - $hashFileIdHelper = new HashFileIDHelper(), - new LegacyFileIDHelper(), - $defaultFileIDHelper = new NaturalFileIDHelper(), - ]); - $fileIDHelperResolutionStrategy->setDefaultFileIDHelper($defaultFileIDHelper); - - // set fileID to the filepath relative to assets dir - $pattern = sprintf('#^/?(%s/?)?#', ASSETS_DIR); - $fileID = preg_replace($pattern ?? '', '', $src ?? ''); - - // Our file reference might be using invalid file name that will have been cleaned up by the migration task. - $fileID = $defaultFileIDHelper->cleanFilename($fileID); - - // Try resolving with public filesystem first - $filesystem = $this->flysystemAssetStore->getPublicFilesystem(); - $parsedFileId = $fileIDHelperResolutionStrategy->resolveFileID($fileID, $filesystem); - if (!$parsedFileId) { - // Try resolving with protected filesystem - $filesystem = $this->flysystemAssetStore->getProtectedFilesystem(); - $parsedFileId = $fileIDHelperResolutionStrategy->resolveFileID($fileID, $filesystem); - } - - if (!$parsedFileId) { - return null; - } - - $parsedFileId = $parsedFileId->setVariant(""); - $newFileId = $hashFileIdHelper->buildFileID($parsedFileId->getFilename(), $parsedFileId->getHash()); - return $parsedFileId - ->setVariant("") - ->setFileID($newFileId); - } - - /** - * @param string $tag - * @return string|null Returns the new tag or null if the tag does not need to be rewritten - */ - private function getNewTag($tag) - { - $tuple = $this->getTagTuple($tag); - if (!isset($tuple['tagType']) || !isset($tuple['src'])) { - return null; - } - $tagType = strtolower($tuple['tagType'] ?? ''); - $src = $tuple['src'] ?: $tuple['href']; - - // Search for a File object containing this filename - $parsedFileID = $this->getParsedFileIDFromSrc($src); - if (!$parsedFileID) { - return null; - } - - /** @var File $file */ - $file = File::get()->filter('FileFilename', $parsedFileID->getFilename())->first(); - if (!$file && class_exists(Versioned::class)) { - $file = Versioned::withVersionedMode(function () use ($parsedFileID) { - Versioned::set_stage(Versioned::LIVE); - return File::get()->filter('FileFilename', $parsedFileID->getFilename())->first(); - }); - } - - if ($parsedFileID && $file) { - if ($tagType == 'img') { - $find = [ - '/(<|\[)img/i', - '/src\s*=\s*(?:"|\').*?(?:"|\')/i', - '/href\s*=\s*(?:"|\').*?(?:"|\')/i', - '/id\s*=\s*(?:"|\').*?(?:"|\')/i', - '/\s*(\/?>|\])/', - ]; - $replace = [ - '[image', - "src=\"/".ASSETS_DIR."/{$parsedFileID->getFileID()}\"", - "href=\"/".ASSETS_DIR."/{$parsedFileID->getFileID()}\"", - "", - " id=\"{$file->ID}\"]", - ]; - $shortcode = preg_replace($find ?? '', $replace ?? '', $tag ?? ''); - } elseif ($tagType == 'a') { - $attribute = 'href'; - $find = "/$attribute\s*=\s*(?:\"|').*?(?:\"|')/i"; - $replace = "$attribute=\"[file_link,id={$file->ID}]\""; - $shortcode = preg_replace($find ?? '', $replace ?? '', $tag ?? ''); - } else { - return null; - } - return $shortcode; - } - return null; - } - - /** - * Returns an array of the fields available for the provided class and its sub-classes as follows: - * - * [ - * 'ClassName' => [ - * 'TableName' => [ - * 'FieldName', - * 'FieldName2', - * ], - * 'TableName2' => [ - * 'FieldName3', - * ], - * ], - * ] - * - * - * @param string|object $baseClass - * @param bool $includeBaseClass Whether to include fields in the base class or not - * @param string|string[] $fieldNames The field to get mappings for, for example 'HTMLText'. Can also be an array. - * Subclasses of DBFields must be defined explicitely. - * @return array An array of fields that derivec from $baseClass. - * @throws \ReflectionException - */ - private function getFieldMap($baseClass, $includeBaseClass, $fieldNames) - { - $mapping = []; - // Normalise $fieldNames to a string array - if (is_string($fieldNames)) { - $fieldNames = [$fieldNames]; - } - // Add the FQNS classnames of the DBFields - $extraFieldNames = []; - foreach ($fieldNames as $fieldName) { - $dbField = Injector::inst()->get($fieldName); - if ($dbField && $dbField instanceof DBField) { - $extraFieldNames[] = get_class($dbField); - } - } - $fieldNames = array_merge($fieldNames, $extraFieldNames); - foreach (ClassInfo::subclassesFor($baseClass, $includeBaseClass) as $class) { - /** @var DataObjectSchema $schema */ - $schema = singleton($class)->getSchema(); - /** @var DataObject $fields */ - $fields = $schema->fieldSpecs($class, DataObjectSchema::DB_ONLY|DataObjectSchema::UNINHERITED); - - foreach ($fields as $field => $type) { - $type = preg_replace('/\(.*\)$/', '', $type ?? ''); - if (in_array($type, $fieldNames ?? [])) { - $table = $schema->tableForField($class, $field); - if (!isset($mapping[$class])) { - $mapping[$class] = []; - } - if (!isset($mapping[$class][$table])) { - $mapping[$class][$table] = []; - } - if (!in_array($field, $mapping[$class][$table] ?? [])) { - $mapping[$class][$table][] = $field; - } - } - } - } - return $mapping; - } -} diff --git a/src/Dev/Tasks/TagsToShortcodeTask.php b/src/Dev/Tasks/TagsToShortcodeTask.php deleted file mode 100644 index 11c59d37..00000000 --- a/src/Dev/Tasks/TagsToShortcodeTask.php +++ /dev/null @@ -1,52 +0,0 @@ -get(FileHashingService::class)->enableCache(); - - $tagsToShortcodeHelper = new TagsToShortcodeHelper( - $request->getVar('baseClass'), - isset($request->getVars()['includeBaseClass']) - ); - $tagsToShortcodeHelper->run(); - - echo 'DONE'; - } -} diff --git a/src/Dev/Tasks/VersionedFilesMigrationTask.php b/src/Dev/Tasks/VersionedFilesMigrationTask.php deleted file mode 100644 index 86376376..00000000 --- a/src/Dev/Tasks/VersionedFilesMigrationTask.php +++ /dev/null @@ -1,42 +0,0 @@ -getVar('strategy') ?: self::STRATEGY_DELETE; - $migrator = VersionedFilesMigrator::create($strategy, ASSETS_PATH, true); - $migrator->migrate(); - } -} diff --git a/src/Dev/TestAssetStore.php b/src/Dev/TestAssetStore.php index 1208d4d2..fdcd8d38 100644 --- a/src/Dev/TestAssetStore.php +++ b/src/Dev/TestAssetStore.php @@ -15,6 +15,9 @@ use SilverStripe\Assets\Storage\AssetStoreRouter; use SilverStripe\Assets\Storage\DBFile; use SilverStripe\Assets\File; +use SilverStripe\Assets\FilenameParsing\FileIDHelper; +use SilverStripe\Assets\FilenameParsing\HashFileIDHelper; +use SilverStripe\Assets\FilenameParsing\ParsedFileID; use SilverStripe\Assets\Flysystem\Filesystem; use SilverStripe\Assets\Folder; use SilverStripe\Assets\Storage\GeneratedAssetHandler; @@ -160,9 +163,11 @@ public static function getLocalPath(AssetContainer $asset, $forceProtected = fal return $relative ? $fileID : $adapter->prefixPath($fileID); } - public function cleanFilename($filename) + public function cleanFilename(string $filename) { - return parent::cleanFilename($filename); + /** @var FileIDHelper $helper */ + $helper = Injector::inst()->get(HashFileIDHelper::class); + return $helper->cleanFilename($filename); } public function getFileID($filename, $hash, $variant = null) @@ -170,24 +175,39 @@ public function getFileID($filename, $hash, $variant = null) return parent::getFileID($filename, $hash, $variant); } - public function parseFileID($fileID) + public function parseFileID(string $fileID) { - return parent::parseFileID($fileID); + /** @var ParsedFileID $parsedFileID */ + $parsedFileID = $this->getProtectedResolutionStrategy()->parseFileID($fileID); + return $parsedFileID ? $parsedFileID->getTuple() : null; } - public function getOriginalFilename($fileID) + public function getOriginalFilename(string $fileID) { - return parent::getOriginalFilename($fileID); + /** @var ParsedFileID $parsedFileID */ + $parsedFiledID = $this->getPublicResolutionStrategy()->parseFileID($fileID); + return $parsedFiledID ? $parsedFiledID->getFilename() : null; } - public function getFilesystemFor($fileID) + public function getFilesystemFor(string $fileID) { - return parent::getFilesystemFor($fileID); + return $this->applyToFileIDOnFilesystem( + function (ParsedFileID $parsedFileID, Filesystem $fs) { + return $fs; + }, + $fileID + ); } - public function removeVariant($fileID) + public function removeVariant(string $fileID) { - return parent::removeVariant($fileID); + /** @var ParsedFileID $parsedFileID */ + $parsedFiledID = $this->getPublicResolutionStrategy()->parseFileID($fileID); + if ($parsedFiledID) { + return $this->getPublicResolutionStrategy()->buildFileID($parsedFiledID->setVariant('')); + } + + return $fileID; } public function getDefaultConflictResolution($variant) diff --git a/src/Dev/VersionedFilesMigrator.php b/src/Dev/VersionedFilesMigrator.php deleted file mode 100644 index aabc3d70..00000000 --- a/src/Dev/VersionedFilesMigrator.php +++ /dev/null @@ -1,196 +0,0 @@ - '%$' . Finder::class, - ]; - - /** - * @var Finder - */ - private $finder; - - /** - * @var string - */ - private $basePath = ASSETS_DIR; - - /** - * @var string - */ - private $strategy = self::STRATEGY_DELETE; - - /** - * @var bool - */ - private $showOutput = true; - - /** - * List of logged messages, if $showOutput is false - * @var array - */ - private $log = []; - - /** - * VersionedFilesMigrationTask constructor. - * @param string $strategy - * @param string $basePath - * @param bool $output - */ - public function __construct($strategy = self::STRATEGY_DELETE, $basePath = ASSETS_DIR, $output = true) - { - Deprecation::notice('1.12.0', 'Will be removed without equivalent functionality to replace it', Deprecation::SCOPE_CLASS); - - if (!in_array($strategy, [self::STRATEGY_DELETE, self::STRATEGY_PROTECT])) { - throw new InvalidArgumentException(sprintf( - 'Invalid strategy: %s', - $strategy - )); - } - $this->basePath = $basePath; - $this->strategy = $strategy; - $this->showOutput = $output; - } - - /** - * @return void - */ - public function migrate() - { - if ($this->strategy === self::STRATEGY_PROTECT) { - $this->doProtect(); - } else { - $this->doDelete(); - } - } - - /** - * @return void - */ - private function doProtect() - { - foreach ($this->getVersionDirectories() as $path) { - $htaccessPath = Path::join($path, '.htaccess'); - if (!file_exists($htaccessPath ?? '')) { - $content = "Require all denied"; - @file_put_contents($htaccessPath ?? '', $content); - if (file_exists($htaccessPath ?? '')) { - $this->output("Added .htaccess file to $htaccessPath"); - } else { - $this->output("Failed to add .htaccess file to $htaccessPath"); - } - } - } - } - - /** - * @return void - */ - private function doDelete() - { - foreach ($this->getVersionDirectories() as $path) { - if (!is_dir($path ?? '')) { - continue; - } - - Filesystem::removeFolder($path); - - if (!is_dir($path ?? '')) { - $this->output("Deleted $path"); - } else { - $this->output("Failed to delete $path"); - } - } - } - - /** - * @return array - */ - private function getVersionDirectories() - { - $results = $this - ->getFinder() - ->directories() - ->name('_versions') - ->in($this->basePath); - - $folders = []; - - /* @var SplFileInfo $result */ - foreach ($results as $result) { - $folders[] = $result->getPathname(); - } - - return $folders; - } - - /** - * @return string - */ - private function nl() - { - return Director::is_cli() ? PHP_EOL : "
"; - } - - /** - * @param string $msg - */ - private function output($msg) - { - if ($this->showOutput) { - echo $msg . $this->nl(); - } else { - $this->log[] = $msg; - } - } - - /** - * @param Finder $finder - * @return $this - */ - public function setFinder(Finder $finder) - { - $this->finder = $finder; - - return $this; - } - - /** - * @return Finder - */ - public function getFinder() - { - return $this->finder; - } - - /** - * @return array - */ - public function getLog() - { - return $this->log; - } -} diff --git a/src/File.php b/src/File.php index cb26022e..1652438b 100644 --- a/src/File.php +++ b/src/File.php @@ -15,7 +15,6 @@ use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Core\Resettable; -use SilverStripe\Dev\Deprecation; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\HTMLReadonlyField; use SilverStripe\Forms\TextField; @@ -343,15 +342,6 @@ public function Link() return $this->getURL(); } - /** - * @deprecated 1.0.0 Use getURL() instead - */ - public function RelativeLink() - { - Deprecation::notice('1.0.0', 'Use getURL() instead'); - return Director::makeRelative($this->getURL()); - } - /** * Just an alias function to keep a consistent API with SiteTree * @@ -1120,19 +1110,6 @@ public static function format_size($size) return round(($size/(1024*1024*1024))*10)/10 . ' GB'; } - /** - * Convert a php.ini value (eg: 512M) to bytes - * - * @deprecated 1.12.0 Use Convert::memstring2bytes() instead - * @param string $iniValue - * @return int - */ - public static function ini2bytes($iniValue) - { - Deprecation::notice('1.12.0', 'Use Convert::memstring2bytes() instead'); - return Convert::memstring2bytes($iniValue); - } - /** * Return file size in bytes. * diff --git a/src/FileMigrationHelper.php b/src/FileMigrationHelper.php deleted file mode 100644 index 9b28d17b..00000000 --- a/src/FileMigrationHelper.php +++ /dev/null @@ -1,11 +0,0 @@ -failNewerVariant = $failNewerVariant; - } - - - public function buildFileID($filename, $hash = null, $variant = null, $cleanfilename = true) - { - if ($filename instanceof ParsedFileID) { - $variant = $filename->getVariant(); - $filename = $filename->getFilename(); - } - - $name = basename($filename ?? ''); - - // Split extension - $extension = null; - if (($pos = strpos($name ?? '', '.')) !== false) { - $extension = substr($name ?? '', $pos ?? 0); - $name = substr($name ?? '', 0, $pos); - } - - $fileID = $name; - - // Add directory - $dirname = ltrim(dirname($filename ?? ''), '.'); - - // Add variant - if ($variant) { - $fileID = '_resampled/' . str_replace('_', '/', $variant ?? '') . '/' . $fileID; - } - - if ($dirname) { - $fileID = $dirname . '/' . $fileID; - } - - // Add extension - if ($extension) { - $fileID .= $extension; - } - - return $fileID; - } - - public function cleanFilename($filename) - { - // Swap backslash for forward slash - $filename = str_replace('\\', '/', $filename ?? ''); - - // There's not really any relevant cleaning rule for legacy. It's not important any way because we won't be - // generating legacy URLs, aside from maybe for testing. - return $filename; - } - - /** - * @note LegacyFileIDHelper is meant to fail when parsing newer format fileIDs with a variant e.g.: - * `subfolder/abcdef7890/sam__resizeXYZ.jpg`. When parsing fileIDs without a variant, it should return the same - * results as natural paths. This behavior can be disabled by setting `failNewerVariant` to false on the - * constructor. - */ - public function parseFileID($fileID) - { - if ($this->failNewerVariant) { - $pattern = '#^(?([^/]+/)*?)(_resampled/(?([^.]+))/)?((?((?(\..+)*)$#i'; - } else { - $pattern = '#^(?([^/]+/)*?)(_resampled/(?([^.]+))/)?((?([^/.])+))(?(\..+)*)$#i'; - } - - // not a valid file (or not a part of the filesystem) - if (!preg_match($pattern ?? '', $fileID ?? '', $matches)) { - return null; - } - - // Can't have a resampled folder without a variant - if (empty($matches['variant']) && strpos($fileID ?? '', '_resampled') !== false) { - return $this->parseSilverStripe30VariantFileID($fileID); - } - - $filename = $matches['folder'] . $matches['basename'] . $matches['extension']; - return new ParsedFileID( - $filename, - '', - isset($matches['variant']) ? str_replace('/', '_', $matches['variant']) : '', - $fileID - ); - } - - /** - * Try to parse a FileID as a pre-SS33 variant. From SS3.0 to SS3.2 the variants were prefixed in the file name, - * rather than encoded into folders. - * @param string $fileID Variant file ID. Variantless FileID should have been parsed by `parseFileID`. - * @return ParsedFileID|null - */ - private function parseSilverStripe30VariantFileID($fileID) - { - $ss3Methods = $this->getImageVariantMethods(); - $variantPartialRegex = implode('|', $ss3Methods); - - if ($this->failNewerVariant) { - $pattern = '#^(?([^/]+/)*?)(_resampled/(?((((' . $variantPartialRegex . ')[^.-]+))-)+))?((?((?(\..+)*)$#i'; - } else { - $pattern = '#^(?([^/]+/)*?)(_resampled/(?((((' . $variantPartialRegex . ')[^.-]+))-)+))?((?([^/.])+))(?(\..+)*)$#i'; - } - - // not a valid file (or not a part of the filesystem) - if (!preg_match($pattern ?? '', $fileID ?? '', $matches)) { - return null; - } - - // Our SS3 variant can be confused with regular filenames, let's minimise the risk of this by making - // sure all our variants use a valid SS3 variant expression - $variant = trim($matches['variant'] ?? '', '-'); - $possibleVariants = explode('-', $variant ?? ''); - $validVariants = []; - $validVariantRegex = '#^(' . $variantPartialRegex . ')(?(.+))$#i'; - - // Loop through the possible variants until we find an invalid one - while ($possible = array_shift($possibleVariants)) { - // Find the base64 encoded argument attached to the image method - if (preg_match($validVariantRegex ?? '', $possible ?? '', $variantMatches)) { - try { - // Our base 64 encoded string always decodes to a string representation of php array - // So we're assuming it always starts with a `[` and ends with a `]` - $base64Str = $variantMatches['base64']; - $argumentString = base64_decode($base64Str ?? ''); - if ($argumentString && preg_match('/^\[.*\]$/', $argumentString ?? '')) { - $validVariants[] = $possible; - continue; - } - } catch (Exception $ex) { - // If we get an error in the regex or in the base64 decode, assume our possible variant is invalid. - } - } - array_unshift($possibleVariants, $possible); - break; - } - - - // Can't have a resampled folder without a variant - if (empty($validVariants)) { - return null; - } - - // Reconcatenate our variants - $variant = implode('_', $validVariants); - - // Our invalid variants are part of the filename - $invalidVariant = $possibleVariants ? implode('-', $possibleVariants) . '-' : ''; - $filename = $matches['folder'] . $invalidVariant . $matches['basename'] . $matches['extension']; - - return new ParsedFileID($filename, '', $variant, $fileID); - } - - - /** - * Get a list of possible variant methods. - * @return string[] - */ - private function getImageVariantMethods() - { - $variantMethods = self::config()->get('ss3_image_variant_methods'); - // Sort the variant methods by descending order of string length. - // This is important because the regex will match the string in order of appearance. - // e.g. `paddedimageW10` could be confused for `pad` with a base64 string of `dedimageW10` - usort($variantMethods, function ($a, $b) { - return strlen($b ?? '') - strlen($a ?? ''); - }); - - return $variantMethods; - } - - public function isVariantOf($fileID, ParsedFileID $original) - { - $variant = $this->parseFileID($fileID); - return $variant && $variant->getFilename() == $original->getFilename(); - } - - public function lookForVariantIn(ParsedFileID $parsedFileID) - { - $folder = dirname($parsedFileID->getFilename() ?? ''); - if ($folder == '.') { - $folder = ''; - } else { - $folder .= '/'; - } - return $folder . '_resampled'; - } - - public function lookForVariantRecursive(): bool - { - return true; - } -} diff --git a/src/FilenameParsing/NaturalFileIDHelper.php b/src/FilenameParsing/NaturalFileIDHelper.php index 42d825bf..a3055626 100644 --- a/src/FilenameParsing/NaturalFileIDHelper.php +++ b/src/FilenameParsing/NaturalFileIDHelper.php @@ -7,8 +7,7 @@ /** * Parsed Natural path URLs. Natural path is the same hashless path that appears in the CMS. * - * Natural paths are used by the public adapter from SilverStripe 4.4 and on the protected adapter when - * `legacy_filenames` is enabled. + * Natural paths are used by the public adapter from SilverStripe 4.4 * * e.g.: `Uploads/sam__ResizedImageWzYwLDgwXQ.jpg` */ diff --git a/src/Filesystem.php b/src/Filesystem.php index 4d4e388c..b1a8d693 100644 --- a/src/Filesystem.php +++ b/src/Filesystem.php @@ -5,9 +5,6 @@ use FilesystemIterator; use SilverStripe\Core\Config\Configurable; use SilverStripe\Control\Director; -use SilverStripe\Dev\Deprecation; -use SilverStripe\Security\Permission; -use SilverStripe\Security\Security; use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; /** @@ -108,27 +105,6 @@ public static function remove_folder_if_empty($folder, $recursive = true) return true; } - /** - * Cleanup function to reset all the Filename fields. Visit File/fixfiles to call. - * - * @deprecated 1.12.0 Will be removed without equivalent functionality to replace it - */ - public function fixfiles() - { - Deprecation::notice('1.12.0', 'Will be removed without equivalent functionality to replace it'); - if (!Permission::check('ADMIN')) { - return Security::permissionFailure($this); - } - - $files = File::get(); - foreach ($files as $file) { - $file->updateFilesystem(); - echo "
  • ", $file->Filename; - $file->write(); - } - echo "

    Done!"; - } - /** * Return the most recent modification time of anything in the folder. * diff --git a/src/Flysystem/FlysystemAssetStore.php b/src/Flysystem/FlysystemAssetStore.php index 4072cf2b..5dd8561e 100644 --- a/src/Flysystem/FlysystemAssetStore.php +++ b/src/Flysystem/FlysystemAssetStore.php @@ -24,7 +24,6 @@ use SilverStripe\Core\Extensible; use SilverStripe\Core\Flushable; use SilverStripe\Core\Injector\Injector; -use SilverStripe\Dev\Deprecation; use SilverStripe\Security\Security; use SilverStripe\Versioned\Versioned; @@ -65,27 +64,6 @@ class FlysystemAssetStore implements AssetStore, AssetStoreRouter, Flushable */ private $protectedResolutionStrategy = null; - /** - * Enable to use legacy filename behaviour (omits hash and uses the natural filename). - * - * This setting was only required for SilverStripe prior to the 4.4.0 release. - * This release re-introduced natural filenames as the default mode for public files. - * See https://docs.silverstripe.org/en/4/developer_guides/files/file_migration/ - * and https://docs.silverstripe.org/en/4/changelogs/4.4.0/ for details. - * - * If you have migrated to 4.x prior to the 4.4.0 release with this setting turned on, - * the setting won't have any effect starting with this release. - * - * If you have migrated to 4.x prior to the 4.4.0 release with this setting turned off, - * we recommend that you run the file migration task as outlined - * in https://docs.silverstripe.org/en/4/changelogs/4.4.0/ - * - * @config - * @deprecated 1.4.0 Legacy file names will not be supported in Silverstripe CMS 5 - * @var bool - */ - private static $legacy_filenames = false; - /** * Flag if empty folders are allowed. * If false, empty folders are cleared up when their contents are deleted. @@ -247,24 +225,6 @@ public function setProtectedResolutionStrategy(FileResolutionStrategy $protected $this->protectedResolutionStrategy = $protectedResolutionStrategy; } - /** - * Return the store that contains the given fileID - * - * @param string $fileID Internal file identifier - * @deprecated 1.4.0 Use `applyToFileIDOnFilesystem()` instead - * @return Filesystem - */ - protected function getFilesystemFor($fileID) - { - Deprecation::notice('1.4.0', 'Use `applyToFileIDOnFilesystem()` instead'); - return $this->applyToFileIDOnFilesystem( - function (ParsedFileID $parsedFileID, Filesystem $fs) { - return $fs; - }, - $fileID - ); - } - /** * Generic method to apply an action to a file regardless of what FileSystem it's on. The action to perform should * be provided as a closure expecting the following signature: @@ -693,27 +653,6 @@ function (ParsedFileID $pfid, Filesystem $fs, FileResolutionStrategy $strategy) return $newParsedFiledID ? $newParsedFiledID->getFilename(): null; } - /** - * Delete the given file (and any variants) in the given {@see Filesystem} - * - * @param string $fileID - * @param Filesystem $filesystem - * @return bool True if a file was deleted - * @deprecated 1.4.0 Use `deleteFromFileStore()` instead - */ - protected function deleteFromFilesystem($fileID, Filesystem $filesystem) - { - Deprecation::notice('1.4.0', 'Use `deleteFromFileStore()` instead'); - $deleted = false; - foreach ($this->findVariants($fileID, $filesystem) as $nextID) { - $filesystem->delete($nextID); - $deleted = true; - } - - return $deleted; - } - - /** * Delete the given file (and any variants) in the given {@see Filesystem} * @param ParsedFileID $parsedFileID @@ -758,31 +697,6 @@ protected function truncateDirectory($dirname, Filesystem $filesystem) } } - /** - * Returns an iterable {@see Generator} of all files / variants for the given $fileID in the given $filesystem - * This includes the empty (no) variant. - * - * @param string $fileID ID of original file to compare with. - * @param Filesystem $filesystem - * @return Generator - * @deprecated 1.12.0 Use FileResolutionStrategy::findVariants() instead - */ - protected function findVariants($fileID, Filesystem $filesystem) - { - Deprecation::notice('1.12.0', 'Use FileResolutionStrategy::findVariants() instead'); - $dirname = ltrim(dirname($fileID ?? ''), '.'); - foreach ($filesystem->listContents($dirname)->toArray() as $next) { - if ($next['type'] !== 'file') { - continue; - } - $nextID = $next['path']; - // Compare given file to target, omitting variant - if ($fileID === $this->removeVariant($nextID)) { - yield $nextID; - } - } - } - public function publish($filename, $hash) { if ($this->getVisibility($filename, $hash) === AssetStore::VISIBILITY_PUBLIC) { @@ -902,31 +816,6 @@ public function protect($filename, $hash) ); } - /** - * Move a file (and its associative variants) between filesystems - * - * @param string $fileID - * @param Filesystem $from - * @param Filesystem $to - * @deprecated 1.4.0 Use moveBetweenFileStore() instead - */ - protected function moveBetweenFilesystems($fileID, Filesystem $from, Filesystem $to) - { - Deprecation::notice('1.4.0', 'Use moveBetweenFileStore() instead'); - foreach ($this->findVariants($fileID, $from) as $nextID) { - // Copy via stream - $stream = $from->readStream($nextID); - $to->writeStream($nextID, $stream); - if (is_resource($stream)) { - fclose($stream); - } - $from->delete($nextID); - } - - // Truncate empty dirs - $this->truncateDirectory(dirname($fileID ?? ''), $from); - } - /** * Move a file and its associated variant from one file store to another adjusting the file name format. * @param ParsedFileID $parsedFileID @@ -1051,21 +940,6 @@ protected function isGranted($fileID) return false; } - /** - * get sha1 hash from stream - * - * @param resource $stream - * @return string str1 hash - * @deprecated 1.4.0 Use FileHashingService::computeFromStream() instead - */ - protected function getStreamSHA1($stream) - { - Deprecation::notice('1.4.0', 'Use FileHashingService::computeFromStream() instead'); - return Injector::inst() - ->get(FileHashingService::class) - ->computeFromStream($stream); - } - /** * Get stream as a file * @@ -1214,18 +1088,6 @@ protected function getDefaultConflictResolution($variant) return AssetStore::CONFLICT_OVERWRITE; } - /** - * Determine if legacy filenames should be used. This no longuer makes any difference with the introduction of - * FileResolutionStrategies. - * @deprecated 1.4.0 Legacy file names will not be supported in Silverstripe CMS 5 - * @return bool - */ - protected function useLegacyFilenames() - { - Deprecation::notice('1.4.0', 'Legacy file names will not be supported in Silverstripe CMS 5'); - return $this->config()->get('legacy_filenames'); - } - public function getMetadata($filename, $hash, $variant = null) { // If `applyToFileOnFilesystem` calls our closure we'll know for sure that a file exists @@ -1347,84 +1209,6 @@ protected function fileGeneratorFor($fileID) return Injector::inst()->createWithArgs(AssetNameGenerator::class, [$fileID]); } - /** - * Performs filename cleanup before sending it back. - * - * This name should not contain hash or variants. - * - * @param string $filename - * @return string - * @deprecated 1.4.0 Use FileIDHelper::cleanFilename() instead - */ - protected function cleanFilename($filename) - { - Deprecation::notice('1.4.0', 'Use FileIDHelper::cleanFilename() instead'); - /** @var FileIDHelper $helper */ - $helper = Injector::inst()->get(HashFileIDHelper::class); - return $helper->cleanFilename($filename); - } - - /** - * Get Filename and Variant from FileID - * - * @param string $fileID - * @return array - * @deprecated 1.4.0 Use FileResolutionStrategy::parseFileID() instead - */ - protected function parseFileID($fileID) - { - Deprecation::notice('1.4.0', 'Use FileResolutionStrategy::parseFileID() instead'); - /** @var ParsedFileID $parsedFileID */ - $parsedFileID = $this->getProtectedResolutionStrategy()->parseFileID($fileID); - return $parsedFileID ? $parsedFileID->getTuple() : null; - } - - /** - * Given a FileID, map this back to the original filename, trimming variant and hash - * - * @param string $fileID Adapter specific identifier for this file/version - * @return string Filename for this file, omitting hash and variant - * @deprecated 1.4.0 Use FileResolutionStrategy::parseFileID() and ParsedFileID::getFilename() instead - */ - protected function getOriginalFilename($fileID) - { - Deprecation::notice('1.4.0', 'Use FileResolutionStrategy::parseFileID() and ParsedFileID::getFilename() instead'); - $parsedFiledID = $this->getPublicResolutionStrategy()->parseFileID($fileID); - return $parsedFiledID ? $parsedFiledID->getFilename() : null; - } - - /** - * Get variant from this file - * - * @param string $fileID - * @return string - * @deprecated 1.4.0 Use FileResolutionStrategy::parseFileID() and ParsedFileID::getVariant() instead - */ - protected function getVariant($fileID) - { - Deprecation::notice('1.4.0', 'Use FileResolutionStrategy::parseFileID() and ParsedFileID::getVariant() instead'); - $parsedFiledID = $this->getPublicResolutionStrategy()->parseFileID($fileID); - return $parsedFiledID ? $parsedFiledID->getVariant() : null; - } - - /** - * Remove variant from a fileID - * - * @param string $fileID - * @return string FileID without variant - * @deprecated 1.4.0 Use FileResolutionStrategy::parseFileID() and ParsedFileID::setVariant() instead - */ - protected function removeVariant($fileID) - { - Deprecation::notice('1.4.0', 'Use FileResolutionStrategy::parseFileID() and ParsedFileID::setVariant() instead'); - $parsedFiledID = $this->getPublicResolutionStrategy()->parseFileID($fileID); - if ($parsedFiledID) { - return $this->getPublicResolutionStrategy()->buildFileID($parsedFiledID->setVariant('')); - } - - return $fileID; - } - /** * Map file tuple (hash, name, variant) to a filename to be used by flysystem * diff --git a/src/ImageManipulation.php b/src/ImageManipulation.php index f656a044..534c45c5 100644 --- a/src/ImageManipulation.php +++ b/src/ImageManipulation.php @@ -11,7 +11,6 @@ use SilverStripe\Core\Convert; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\InjectorNotFoundException; -use SilverStripe\Dev\Deprecation; use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\View\AttributesHTML; @@ -952,17 +951,7 @@ public function manipulate($variant, $callback) $result = call_user_func($callback, $store, $filename, $hash, $variant); // Preserve backward compatibility - if (isset($result['Filename'])) { - $tuple = $result; - Deprecation::notice( - '5.0', - 'Closure passed to ImageManipulation::manipulate() should return null or a two-item array - containing a tuple and an image backend, i.e. [$tuple, $result]', - Deprecation::SCOPE_GLOBAL - ); - } else { - list($tuple, $manipulationResult) = $result; - } + list($tuple, $manipulationResult) = $result; } else { $tuple = [ 'Filename' => $filename, diff --git a/src/Shortcodes/FileLinkTracking.php b/src/Shortcodes/FileLinkTracking.php index 5eaa79a7..6f92e8ad 100644 --- a/src/Shortcodes/FileLinkTracking.php +++ b/src/Shortcodes/FileLinkTracking.php @@ -4,7 +4,6 @@ use DOMElement; use SilverStripe\Assets\File; -use SilverStripe\Dev\Deprecation; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FormScaffolder; use SilverStripe\ORM\DataExtension; @@ -65,16 +64,6 @@ class FileLinkTracking extends DataExtension */ private static $show_file_link_tracking = false; - /** - * @deprecated 1.2.0 Use FileTracking() instead - * @return File[]|ManyManyList - */ - public function ImageTracking() - { - Deprecation::notice('1.2.0', 'Use FileTracking() instead'); - return $this->FileTracking(); - } - /** * FileParser for link tracking * diff --git a/tests/php/Dev/Tasks/FileMigrationHelperTest.php b/tests/php/Dev/Tasks/FileMigrationHelperTest.php deleted file mode 100644 index 23e23844..00000000 --- a/tests/php/Dev/Tasks/FileMigrationHelperTest.php +++ /dev/null @@ -1,476 +0,0 @@ - [ - Extension::class, - ] - ]; - - /** - * get the BASE_PATH for this test - * - * @return string - */ - protected function getBasePath() - { - // Note that the actual filesystem base is the 'assets' subdirectory within this - return ASSETS_PATH . '/FileMigrationHelperTest'; - } - - - protected function setUp(): void - { - parent::setUp(); - - // Set backend root to /FileMigrationHelperTest/assets - TestAssetStore::activate('FileMigrationHelperTest/assets'); - $this->makingBadFilesBadAgain(); - - /** @var \League\Flysystem\Filesystem $fs */ - $fs = Injector::inst()->get(AssetStore::class)->getPublicFilesystem(); - - // Ensure that each file has a local record file in this new assets base - $missingID = $this->idFromFixture(File::class, 'missing-file'); - foreach (File::get()->filter('ClassName', File::class)->exclude('ID', $missingID) as $file) { - $filename = $file->generateFilename(); - $this->expectedContent[$file->ID] = 'Content of ' . $filename; - $fs->write($filename, $this->expectedContent[$file->ID]); - } - - // Let's create some variants for our images - $fromMain = fopen(__DIR__ . '/../../ImageTest/test-image-low-quality.jpg', 'r'); - $fromVariant = fopen(__DIR__ . '/../../ImageTest/test-image-high-quality.jpg', 'r'); - foreach (Image::get() as $file) { - $filename = $file->generateFilename(); - rewind($fromMain); - $fs->writeStream($filename, $fromMain); - - $dir = dirname($filename ?? ''); - $basename = basename($filename ?? ''); - - rewind($fromVariant); - $fs->writeStream($dir . '/_resampled/resizeXYZ/' . $basename, $fromVariant); - rewind($fromVariant); - $fs->writeStream($dir . '/_resampled/resizeXYZ/scaleABC/' . $basename, $fromVariant); - rewind($fromVariant); - $fs->writeStream($dir . '/_resampled/ScaleWidthWzEwMF0-' . $basename, $fromVariant); - rewind($fromVariant); - $fs->writeStream($dir . '/_resampled/ScaleWidthWzEwMF0-FitWzEwMCwxMDBd-' . $basename, $fromVariant); - } - fclose($fromMain); - fclose($fromVariant); - - $fs->move('wrong-case.txt', 'wRoNg-CaSe.tXt'); - $fs->move('Uploads/good-case-bad-folder.txt', 'uploads/good-case-bad-folder.txt'); - $fs->copy('too-many-alternative-case.txt', 'Too-Many-Alternative-Case.txt'); - $fs->copy('too-many-alternative-case.txt', 'Too-Many-Alternative-Case.TXT'); - $fs->delete('too-many-alternative-case.txt'); - } - - /** - * When setUp creates fixtures, they go through their normal validation process. This means that our bad file names - * get cleaned up before being written to the DB. We need them to be bad in the DB, so we'll run a bunch of manual - * queries to bypass the ORM. - */ - public function makingBadFilesBadAgain() - { - // Renaming our common bad files - $badnameIDs = array_map( - function ($args) { - list($class, $identifier) = $args; - return $this->idFromFixture($class, $identifier); - }, - [ - [File::class, 'badname'], - [File::class, 'badname2'], - [Image::class, 'badimage'] - ] - ); - SQLUpdate::create( - '"File"', - [ - '"Filename"' => ['REPLACE("Filename",?,?)' => ['_', '__']], - '"Name"' => ['REPLACE("Name",?,?)' => ['_', '__']], - ], - ['"ID" IN (?,?,?)' => $badnameIDs] - )->execute(); - - // badnameconflict needs to be manually updated, because it will have gotten a `-v2` suffix - SQLUpdate::create( - '"File"', - [ - '"Filename"' => 'assets/bad__name.doc', - '"Name"' => 'bad__name.doc', - ], - ['"ID"' => $this->idFromFixture(File::class, 'badnameconflict')] - )->execute(); - - SQLUpdate::create( - '"File"', - [ - '"Filename"' => 'assets/ParentFolder/SubFolder/multi-dash--file---4.pdf', - '"Name"' => 'multi-dash--file---4.pdf', - ], - ['"ID"' => $this->idFromFixture(File::class, 'multi-dash-file')] - )->execute(); - - SQLUpdate::create( - '"File"', - [ - '"Filename"' => 'assets/mixed-case-file.txt', - '"Name"' => 'mixed-case-file.txt', - ], - ['"ID"' => $this->idFromFixture(File::class, 'all-lowercase')] - )->execute(); - - SQLUpdate::create( - '"File"', - [ - '"Filename"' => 'assets/uploads/good-case-bad-folder.txt', - ], - ['"ID"' => $this->idFromFixture(File::class, 'mismatch-folder-case')] - )->execute(); - } - - protected function tearDown(): void - { - TestAssetStore::reset(); - Filesystem::removeFolder($this->getBasePath()); - parent::tearDown(); - } - - /** - * Test file migration - */ - public function testMigration() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $this->preCondition(); - - // The EXE file, extensionless file and missing file won't be migrated - $expectNumberOfMigratedFiles = File::get()->exclude('ClassName', Folder::class)->count() - 4; - - // Do migration - $helper = new FileMigrationHelper(); - $result = $helper->run($this->getBasePath()); - - // Test the top level results - $this->assertEquals($expectNumberOfMigratedFiles, $result); - - // Test that each file exists excluding conflictual file - $files = File::get() - ->exclude('ClassName', Folder::class) - ->exclude('ID', [ - $this->idFromFixture(File::class, 'goodnameconflict'), - $this->idFromFixture(File::class, 'badnameconflict'), - $this->idFromFixture(File::class, 'multi-dash-file'), - $this->idFromFixture(File::class, 'missing-file'), - $this->idFromFixture(File::class, 'too-many-case'), - ]); - - foreach ($files as $file) { - $this->individualFiles($file); - } - - foreach (Image::get() as $image) { - $this->individualImage($image); - } - - $this->invalidFile(); - $this->fileWithNoExtension(); - $this->pdfNormalisedToFile(); - $this->badSS3filenames(); - $this->conflictualBadNames(); - $this->missingFiles(); - } - - /** - * Testing that our set up script has worked as expected. - */ - private function preCondition() - { - // Prior to migration, check that each file has empty Filename / Hash properties - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - $filename = $file->generateFilename(); - $this->assertNotEmpty($filename, "File {$file->Name} has a filename"); - $this->assertEmpty($file->File->getFilename(), "File {$file->Name} has no DBFile filename"); - $this->assertEmpty($file->File->getHash(), "File {$file->Name} has no hash"); - $this->assertFalse($file->exists(), "File with name {$file->Name} does not yet exist"); - $this->assertFalse($file->isPublished(), "File is not published yet"); - } - - $this->assertFileExists( - TestAssetStore::base_path() . '/ParentFolder/SubFolder/myfile.exe', - 'We should have an invalid file before going into the migration.' - ); - } - - /** - * Check the validity of an individual files - * @param File $file - */ - private function individualFiles(File $file) - { - $expectedFilename = $file->generateFilename(); - $filename = $file->File->getFilename(); - $this->assertTrue($file->exists(), "File with name {$filename} exists"); - $this->assertNotEmpty($filename, "File {$file->Name} has a Filename"); -// $this->assertEquals($expectedFilename, $filename, "File {$file->Name} has retained its Filename value"); - - // myimage.pdf starts off as an image, that's why it will have the image hash - if ($file->ClassName == Image::class || $expectedFilename == 'myimage.pdf') { - $this->assertEquals( - '33be1b95cba0358fe54e8b13532162d52f97421c', - $file->File->getHash(), - "File with name {$filename} has the correct hash" - ); - } else { - $expectedContent = $this->expectedContent[$file->ID]; - $this->assertEquals( - $expectedContent, - $file->getString(), - "File with name {$filename} has the correct content" - ); - $this->assertEquals( - sha1($expectedContent ?? ''), - $file->File->getHash(), - "File with name {$filename} has the correct hash" - ); - } - - $this->assertTrue($file->isPublished(), "File is published after migration"); - $this->assertGreaterThan(0, $file->getAbsoluteSize()); - } - - /** - * Checking the validity of an individual image - * @param Image $file - */ - private function individualImage(Image $file) - { - // Test that our image variant got moved correctly - $fullFilename = TestAssetStore::base_path() . '/' . $file->getFilename(); - $dir = dirname($fullFilename ?? ''); - $baseFilename = basename($fullFilename ?? ''); - $this->assertFileDoesNotExist($dir . '/_resampled'); - $this->assertFileExists($dir . '/' . $baseFilename); - - // Test that SS3.3 variants have been migrated - $variantFilename = preg_replace('#^(.*)\.(.*)$#', '$1__resizeXYZ.$2', $baseFilename ?? ''); - $this->assertFileExists($dir . '/' . $variantFilename); - $variantFilename = preg_replace('#^(.*)\.(.*)$#', '$1_scaleABC.$2', $variantFilename ?? ''); - $this->assertFileExists($dir . '/' . $variantFilename); - - // Test that pre SS3.0 variants have been migrated - $variantFilename = preg_replace('#^(.*)\.(.*)$#', '$1__ScaleWidthWzEwMF0.$2', $baseFilename ?? ''); - $this->assertFileExists($dir . '/' . $variantFilename); - $variantFilename = preg_replace('#^(.*)\.(.*)$#', '$1_FitWzEwMCwxMDBd.$2', $variantFilename ?? ''); - $this->assertFileExists($dir . '/' . $variantFilename); - } - - /** - * Ensure that invalid file has been removed during migration. One EXE file should have been removed from the DB - */ - private function invalidFile() - { - $invalidID = $this->idFromFixture(File::class, 'invalid'); - $this->assertNotEmpty($invalidID); - $this->assertNull(File::get()->byID($invalidID)); - } - /** - * Ensure that invalid file has been removed during migration. One file without an extension should have been removed from the DB - */ - private function fileWithNoExtension() - { - $invalidID = $this->idFromFixture(File::class, 'no-extension'); - $this->assertNotEmpty($invalidID); - $this->assertNull(File::get()->byID($invalidID)); - } - - /** - * SS2.4 considered PDFs to be images. We should convert that back to Regular files - */ - private function pdfNormalisedToFile() - { - // - $pdf = File::find('myimage.pdf'); - $this->assertEquals(File::class, $pdf->ClassName, 'Our PDF classnames should have been corrrected'); - } - - /** - * Files with double underscores in their name should have been renamed to have single underscores. - */ - private function badSS3filenames() - { - // Test that SS3 files with invalid SS4 names, get correctly rename - /** @var File $badname */ - $badname = $this->objFromFixture(File::class, 'badname'); - $this->assertEquals('ParentFolder/bad_name.zip', $badname->getFilename()); - $this->assertFileExists(TestAssetStore::base_path() . '/ParentFolder/bad_name.zip'); - $badname2 = $this->objFromFixture(File::class, 'badname2'); - $this->assertEquals('bad_0.zip', $badname2->getFilename()); - $this->assertFileExists(TestAssetStore::base_path() . '/bad_0.zip'); - $badimage = $this->objFromFixture(Image::class, 'badimage'); - $this->assertEquals('bad_image.gif', $badimage->getFilename()); - $this->assertFileExists(TestAssetStore::base_path() . '/bad_image.gif'); - $this->assertFileExists(TestAssetStore::base_path() . '/bad_image__resizeXYZ.gif'); - $this->assertFileExists(TestAssetStore::base_path() . '/bad_image__resizeXYZ_scaleABC.gif'); - - // Test that our multi dash filename that would normally be renamed via the front end is still the same - $badname = $this->objFromFixture(File::class, 'multi-dash-file'); - $this->assertEquals('ParentFolder/SubFolder/multi-dash--file---4.pdf', $badname->getFilename()); - $this->assertFileExists(TestAssetStore::base_path() . '/ParentFolder/SubFolder/multi-dash--file---4.pdf'); - } - - /** - * If you have a bad files who would otherwise be renamed to an existing file, it should get a `-v2` suffix. - */ - private function conflictualBadNames() - { - - $good = $this->objFromFixture(File::class, 'goodnameconflict'); - $bad = $this->objFromFixture(File::class, 'badnameconflict'); - - // Test the names and existence - $this->assertEquals('bad_name.doc', $good->getFilename()); - $this->assertEquals('bad_name-v2.doc', $bad->getFilename()); - $this->assertTrue($good->exists(), 'bad_name.doc should exist'); - $this->assertTrue($bad->exists(), 'bad_name-v2.doc should exist'); - - // Test the content and hashes of the files - $expectedGoodContent = 'Content of bad_name.doc'; - $this->assertEquals( - $expectedGoodContent, - $good->getString(), - "bad_name.doc has the expected content" - ); - $this->assertEquals( - sha1($expectedGoodContent ?? ''), - $good->File->getHash(), - "bad_name.doc has the expected hash" - ); - - $expectedBadContent = 'Content of bad__name.doc'; - $this->assertEquals( - $expectedBadContent, - $bad->getString(), - "bad_name-v2.doc has the expected content" - ); - $this->assertEquals( - sha1($expectedBadContent ?? ''), - $bad->File->getHash(), - "bad_name-v2.doc has the expected hash" - ); - } - - /** - * That that files that could not be migrated are still there. - */ - private function missingFiles() - { - $missingID = $this->idFromFixture(File::class, 'missing-file'); - $missing = File::get()->byID($missingID); - $this->assertNotEmpty($missing, 'Missing file DB entry should still be there'); - $this->assertEmpty($missing->FileFilename, 'Missing file DB entry should not have a FileFilename'); - - $tooManyCaseID = $this->idFromFixture(File::class, 'too-many-case'); - $tooManyCase = File::get()->byID($tooManyCaseID); - $this->assertNotEmpty($tooManyCase, 'file DB entry that could not be migrated should still be there'); - $this->assertEmpty( - $tooManyCase->FileFilename, - 'file DB entry that could not be migrated should not have FileFilename' - ); - - /** @var Filesystem $fs */ - $fs = Injector::inst()->get(AssetStore::class)->getPublicFilesystem(); - $this->assertTrue( - $fs->has('Too-Many-Alternative-Case.txt'), - 'Too-Many-Alternative-Case.txt should still be there' - );$this->assertTrue( - $fs->has('Too-Many-Alternative-Case.TXT'), - 'Too-Many-Alternative-Case.TXT should still be there' - ); - } - - /** - * Run the same battery of test but with legacy file name enabled. - */ - public function testMigrationWithLegacyFilenames() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - Config::modify()->set(FlysystemAssetStore::class, 'legacy_filenames', true); - $this->testMigration(); - } - - /** - * If you're using a public file resolution strategy that doesn't use the LegacyFileMigration, files should not be - * migrated. - */ - public function testInvalidAssetStoreStrategy() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $strategy = FileIDHelperResolutionStrategy::create(); - $strategy->setDefaultFileIDHelper(new HashFileIDHelper()); - $strategy->setResolutionFileIDHelpers([new HashFileIDHelper()]); - - $store = Injector::inst()->get(AssetStore::class); - $store->setPublicResolutionStrategy($strategy); - - // Do migration - $helper = new FileMigrationHelper(); - $result = $helper->run($this->getBasePath()); - - // Test the top level results - $this->assertEquals(0, $result); - } - - /** - * Run the same battery of test but with legacy file name enabled. - */ - public function testCacheFileHashes() - { - /** @var FileHashingService $hasher */ - $hasher = Injector::inst()->get(FileHashingService::class); - $hasher->enableCache(); - $this->testMigration(); - } -} diff --git a/tests/php/Dev/Tasks/FileMigrationHelperTest.yml b/tests/php/Dev/Tasks/FileMigrationHelperTest.yml deleted file mode 100644 index ed6d3765..00000000 --- a/tests/php/Dev/Tasks/FileMigrationHelperTest.yml +++ /dev/null @@ -1,71 +0,0 @@ -SilverStripe\Assets\Folder: - parent: - Name: ParentFolder - subfolder: - Name: SubFolder - Parent: =>SilverStripe\Assets\Folder.parent - uploads-folder: - Name: Uploads -SilverStripe\Assets\Image: - image1: - Name: myimage.jpg - image2: - Name: myimage.jpg - ParentID: =>SilverStripe\Assets\Folder.subfolder - ## SS2 would sometime treat PDF files as images - fakePdf: - Name: myimage.pdf - ## Image with an invalid SS4 name - badimage: - Name: bad__image.gif -SilverStripe\Assets\File: - file1: - Name: anotherfile.txt - file2: - Name: file.doc - ParentID: =>SilverStripe\Assets\Folder.parent - file3: - Name: picture.pdf - ParentID: =>SilverStripe\Assets\Folder.subfolder - # The front end will want to rename this file - multi-dash-file: - Name: multi-dash--file---0.pdf - ParentID: =>SilverStripe\Assets\Folder.subfolder - # We won't create a physical file for entry. Our process should carry on. - missing-file: - Name: missing-file.pdf - # SS4 doesn't allow files with double underscores - badname: - Name: bad__name.zip - ParentID: =>SilverStripe\Assets\Folder.parent - badname2: - Name: bad__0.zip - # EXE files are not allowed - invalid: - Name: myfile.exe - ParentID: =>SilverStripe\Assets\Folder.subfolder - # Files with no extension are not allowed - no-extension: - Name: myfile - ParentID: =>SilverStripe\Assets\Folder.subfolder - # badnameconflict will want to override goodnameconflict - goodnameconflict: - Name: bad_name.doc - badnameconflict: - Name: bad__name.doc - # name with wrong case - these file will be written to disk with a different case fileID - wrongcase: - Name: wrong-case.txt - mismatch-folder-case: - Name: good-case-bad-folder.txt - ParentID: =>SilverStripe\Assets\Folder.uploads-folder - good-match-folder-case: - Name: good-case-good-folder.txt - ParentID: =>SilverStripe\Assets\Folder.uploads-folder - too-many-case: - Name: too-many-alternative-case.txt - # Case conflict, 2 files with the same name in different case - all-uppercase: - Name: MIXED-CASE-FILE.TXT - all-lowercase: - Name: mixed-case-file.txt diff --git a/tests/php/Dev/Tasks/FileMigrationHelperTest/Extension.php b/tests/php/Dev/Tasks/FileMigrationHelperTest/Extension.php deleted file mode 100644 index fa85dc79..00000000 --- a/tests/php/Dev/Tasks/FileMigrationHelperTest/Extension.php +++ /dev/null @@ -1,26 +0,0 @@ - "Text", - ]; - - public function onBeforeWrite() - { - // Ensure underlying filename field is written to the database - $this->owner->setField('Filename', 'assets/' . $this->owner->generateFilename()); - } -} diff --git a/tests/php/Dev/Tasks/FixFolderPermissionsHelperTest.php b/tests/php/Dev/Tasks/FixFolderPermissionsHelperTest.php deleted file mode 100644 index b68d0167..00000000 --- a/tests/php/Dev/Tasks/FixFolderPermissionsHelperTest.php +++ /dev/null @@ -1,30 +0,0 @@ -markTestSkipped('Test calls deprecated code'); - } - $task = new FixFolderPermissionsHelper(); - $updated = $task->run(); - - $this->assertEquals('Inherit', Folder::get()->filter('Name', 'ParentFolder')->first()->CanViewType); - $this->assertEquals('Anyone', Folder::get()->filter('Name', 'SubFolder')->first()->CanViewType); - $this->assertEquals('Inherit', Folder::get()->filter('Name', 'AnotherFolder')->first()->CanViewType); - $this->assertEquals(2, $updated); - } -} diff --git a/tests/php/Dev/Tasks/FixFolderPermissionsHelperTest.yml b/tests/php/Dev/Tasks/FixFolderPermissionsHelperTest.yml deleted file mode 100644 index 401328fa..00000000 --- a/tests/php/Dev/Tasks/FixFolderPermissionsHelperTest.yml +++ /dev/null @@ -1,11 +0,0 @@ -SilverStripe\Assets\Folder: - parent: - Name: ParentFolder - CanViewType: NULL - subfolder: - Name: SubFolder - Parent: =>SilverStripe\Assets\Folder.parent - CanViewType: Anyone - anotherfolder: - Name: AnotherFolder - CanViewType: diff --git a/tests/php/Dev/Tasks/LegacyThumbnailMigrationHelperTest.php b/tests/php/Dev/Tasks/LegacyThumbnailMigrationHelperTest.php deleted file mode 100644 index e32eac06..00000000 --- a/tests/php/Dev/Tasks/LegacyThumbnailMigrationHelperTest.php +++ /dev/null @@ -1,404 +0,0 @@ - [ - Extension::class, - ] - ]; - - /** - * get the BASE_PATH for this test - * - * @return string - */ - protected function getBasePath() - { - // Note that the actual filesystem base is the 'assets' subdirectory within this - return $this->joinPaths(ASSETS_PATH, '/LegacyThumbnailMigrationHelperTest'); - } - - - protected function setUp(): void - { - parent::setUp(); - - // Set backend root to /LegacyThumbnailMigrationHelperTest/assets - TestAssetStore::activate('LegacyThumbnailMigrationHelperTest/assets'); - - // Ensure that each file has a local record file in this new assets base - $from = $this->joinPaths(__DIR__, '../../ImageTest/test-image-low-quality.jpg'); - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - /** @var $file File */ - $file->setFromLocalFile($from, $file->generateFilename()); - $file->write(); - $file->publishFile(); - $dest = $this->joinPaths(TestAssetStore::base_path(), $file->generateFilename()); - Filesystem::makeFolder(dirname($dest ?? '')); - copy($from ?? '', $dest ?? ''); - } - } - - protected function tearDown(): void - { - TestAssetStore::reset(); - Filesystem::removeFolder($this->getBasePath()); - parent::tearDown(); - } - - /** - * @dataProvider coreVersionsProvider - */ - public function testMigratesWithExistingThumbnailInNewLocation($coreVersion) - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var TestAssetStore $store */ - $store = singleton(AssetStore::class); // will use TestAssetStore - - /** @var Image $image */ - $image = $this->objFromFixture(Image::class, 'nested'); - - $formats = ['ResizedImage' => [60,60]]; - $expectedNewPath = $this->getNewResampledPath($image, $formats, $keep = true); - $expectedLegacyPath = $this->createLegacyResampledImageFixture($store, $image, $formats, $coreVersion); - - $helper = new LegacyThumbnailMigrationHelper(); - $moved = $helper->run($store); - $this->assertCount(0, $moved); - - // Moved contains store relative paths - $base = TestAssetStore::base_path(); - - $this->assertFileDoesNotExist( - $this->joinPaths($base, $expectedLegacyPath), - 'Legacy file has been removed' - ); - $this->assertFileExists( - $this->joinPaths($base, $expectedNewPath), - 'New file is retained (potentially with newer content)' - ); - } - - /** - * @dataProvider coreVersionsProvider - */ - public function testMigratesMultipleFilesInSameFolder($coreVersion) - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var TestAssetStore $store */ - $store = singleton(AssetStore::class); // will use TestAssetStore - - /** @var Image[] $image */ - $images = [ - $this->objFromFixture(Image::class, 'nested'), - $this->objFromFixture(Image::class, 'nested-sibling') - ]; - - // Use same format for *both* files (edge cases!) - $expected = []; - $formats = ['ResizedImage' => [60,60]]; - foreach ($images as $image) { - $expectedNewPath = $this->getNewResampledPath($image, $formats); - $expectedLegacyPath = $this->createLegacyResampledImageFixture($store, $image, $formats, $coreVersion); - $expected[$expectedLegacyPath] = $expectedNewPath; - } - - $helper = new LegacyThumbnailMigrationHelper(); - $moved = $helper->run($store); - $this->assertCount(2, $moved); - - // Moved contains store relative paths - $base = TestAssetStore::base_path(); - - foreach ($expected as $expectedLegacyPath => $expectedNewPath) { - $this->assertFileDoesNotExist( - $this->joinPaths($base, $expectedLegacyPath), - 'Legacy file has been removed' - ); - $this->assertFileExists( - $this->joinPaths($base, $expectedNewPath), - 'New file is retained (potentially with newer content)' - ); - } - } - - - /** - * @dataProvider dataProvider - */ - public function testMigrate($fixtureId, $formats, $coreVersion) - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var TestAssetStore $store */ - $store = singleton(AssetStore::class); // will use TestAssetStore - - /** @var Image $image */ - $image = $this->objFromFixture(Image::class, $fixtureId); - - // Simulate where the new thumbnail would be created by the system. - // Important to do this *before* creating the legacy file, - // because the LegacyFileIDHelper will pick it up as the "new" location otherwise - $expectedNewPath = $this->getNewResampledPath($image, $formats); - $expectedLegacyPath = $this->createLegacyResampledImageFixture($store, $image, $formats, $coreVersion); - - $helper = new LegacyThumbnailMigrationHelper(); - $moved = $helper->run($store); - $this->assertCount(1, $moved); - - // Moved contains store relative paths - $base = TestAssetStore::base_path(); - - $this->assertArrayHasKey( - $expectedLegacyPath, - $moved - ); - $this->assertEquals( - $moved[$expectedLegacyPath], - $expectedNewPath, - 'New file is mapped as expected' - ); - $this->assertFileDoesNotExist( - $this->joinPaths($base, $expectedLegacyPath), - 'Legacy file has been removed' - ); - $this->assertFileExists( - $this->joinPaths($base, $expectedNewPath), - 'New file has been created' - ); - $origFolder = $image->Parent()->getFilename(); - $this->assertEquals( - $origFolder ? $origFolder : '.' . DIRECTORY_SEPARATOR, - dirname($expectedNewPath ?? '') . DIRECTORY_SEPARATOR, - 'Thumbnails are created in same folder as original file' - ); - } - - public function dataProvider() - { - return [ - 'Single variant toplevel 3.0.0' => [ - 'toplevel', - ['ResizedImage' => [60,60]], - self::CORE_VERSION_3_0_0 - ], - 'Single variant toplevel >=3.3.0' => [ - 'toplevel', - ['ResizedImage' => [60,60]], - self::CORE_VERSION_3_3_0 - ], - 'Multi variant toplevel 3.0.0' => [ - 'toplevel', - ['ResizedImage' => [60,60], 'CropHeight' => [30]], - self::CORE_VERSION_3_0_0 - ], - 'Multi variant toplevel >=3.3.0' => [ - 'toplevel', - ['ResizedImage' => [60,60], 'CropHeight' => [30]], - self::CORE_VERSION_3_3_0 - ], - 'Multi variant nested 3.0.0' => [ - 'nested', - ['ResizedImage' => [60,60], 'CropHeight' => [30]], - self::CORE_VERSION_3_0_0 - ], - 'Multi variant nested >=3.3.0' => [ - 'nested', - ['ResizedImage' => [60,60], 'CropHeight' => [30]], - self::CORE_VERSION_3_3_0 - ] - ]; - } - - public function coreVersionsProvider() - { - return [ - '3.0.0' => [self::CORE_VERSION_3_0_0], - '3.3.0' => [self::CORE_VERSION_3_3_0] - ]; - } - - /** - * @param AssetStore $store - * @param Image $baseImage - * @param array $formats - * @param string $coreVersion - * @return string Path relative to the asset store root. - */ - protected function createLegacyResampledImageFixture(AssetStore $store, Image $baseImage, $formats, $coreVersion) - { - if ($coreVersion == self::CORE_VERSION_3_0_0) { - $resampledRelativePath = $this->legacyCacheFilenameVersion300($baseImage, $formats); - } elseif ($coreVersion == self::CORE_VERSION_3_3_0) { - $resampledRelativePath = $this->legacyCacheFilenameVersion330($baseImage, $formats); - } else { - throw new \Exception('Invalid $coreVersion'); - } - - // Using raw copy operation since File->copyFile() messes with the _resampled path nane, - // and anything on asset abstraction unhelpfully copies - // existing (new style) variants as well (creating false positives) - $origPath = $this->joinPaths( - TestAssetStore::base_path(), - $baseImage->generateFilename() - ); - $resampledPath = $this->joinPaths( - TestAssetStore::base_path(), - $resampledRelativePath - ); - Filesystem::makeFolder(dirname($resampledPath ?? '')); - copy($origPath ?? '', $resampledPath ?? ''); - - return $resampledRelativePath; - } - - /** - * Replicates the logic of creating >=3.3 style formatted images, - * based on Image->cacheFilename() and Image->generateFormattedImage(). - * Will create folder structures required for this. - * This naming placed files with their original name - * into a subfolder named after the manipulation. - * - * Format: /_resampled/// - * Example: my-folder/_resampled/ResizedImageWzYwMCwyMDld/ScaleHeightWyIxMDAiXQ/image.jpg - * - * @return string Path relative to the asset store root. - */ - protected function legacyCacheFilenameVersion330($image, $formats) - { - $cacheFilename = ''; - - if ($image->Parent()->exists()) { - $cacheFilename = $image->Parent()->Filename; - } - - $cacheFilename = $this->joinPaths($cacheFilename, '_resampled'); - - foreach ($formats as $format => $args) { - $cacheFilename = $this->joinPaths( - $cacheFilename, - $format . Convert::base64url_encode($args) - ); - } - - $cacheFilename = $this->joinPaths( - $cacheFilename, - basename($image->generateFilename() ?? '') - ); - - return $cacheFilename; - } - - /** - * Replicates the logic of creating <3.3 style formatted images, - * based on Image->cacheFilename() and Image->generateFormattedImage(). - * Will create folder structures required for this. - * This naming used composite filenames with their formats. - * - * Format: /_resampled/-- - * Example: my-folder/_resampled/ResizedImageWzYwMCwyMDld-ScaleHeightWyIxMDAiXQ-image.jpg - * - * @return string Path relative to the asset store root. - */ - protected function legacyCacheFilenameVersion300($image, $formats) - { - $cacheFilename = ''; - - if ($image->Parent()->exists()) { - $cacheFilename = $image->Parent()->Filename; - } - - $cacheFilename = $this->joinPaths($cacheFilename, '_resampled'); - - $formatPrefix = ''; - foreach ($formats as $format => $args) { - $formatPrefix .= $format . Convert::base64url_encode($args) . '-'; - } - - $cacheFilename = $this->joinPaths( - $cacheFilename, - $formatPrefix . basename($image->generateFilename() ?? '') - ); - - return $cacheFilename; - } - - /** - * Create a file variant to get its path, but then remove it. - * We want to check that it's moved into the same location - * through the migration task further along the test. - * - * @param Image $image - * @param array $formats - * @return String Path relative to the asset store root - */ - protected function getNewResampledPath(Image $image, $formats, $keep = false) - { - $resampleds = []; - - /** @var Image $newResampledImage */ - $resampled = $image; - - // Perform the manipulation (only to get the resulting path) - foreach ($formats as $format => $args) { - $resampled = call_user_func_array([$resampled, $format], $args ?? []); - $resampleds [] = $resampled; - } - - $path = TestAssetStore::getLocalPath($resampled, false, true); // relative to store - $path = preg_replace('#^/#', '', $path ?? ''); // normalise with other store relative paths - - // Not using File->delete() since that actually deletes the original file, not only variant. - if (!$keep) { - foreach ($resampleds as $resampled) { - unlink(TestAssetStore::getLocalPath($resampled) ?? ''); - } - } - - return $path; - } - - /** - * @return string - */ - protected function joinPaths() - { - $paths = []; - - foreach (func_get_args() as $arg) { - if ($arg !== '') { - $paths[] = $arg; - } - } - - return preg_replace('#/+#', '/', join('/', $paths)); - } -} diff --git a/tests/php/Dev/Tasks/LegacyThumbnailMigrationHelperTest.yml b/tests/php/Dev/Tasks/LegacyThumbnailMigrationHelperTest.yml deleted file mode 100644 index eb9b1dc7..00000000 --- a/tests/php/Dev/Tasks/LegacyThumbnailMigrationHelperTest.yml +++ /dev/null @@ -1,19 +0,0 @@ -SilverStripe\Assets\Folder: - parent: - Name: ParentFolder - subfolder: - Name: SubFolder - Parent: =>SilverStripe\Assets\Folder.parent -SilverStripe\Assets\Image: - toplevel: - Name: toplevel.jpg - nested: - Name: nested.jpg - ParentID: =>SilverStripe\Assets\Folder.subfolder - nested-sibling: - Name: nested-sibling.jpg - ParentID: =>SilverStripe\Assets\Folder.subfolder -SilverStripe\Assets\File: - file1: - Name: anotherfile.jpg - diff --git a/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperTest.php b/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperTest.php deleted file mode 100644 index 460dfe79..00000000 --- a/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperTest.php +++ /dev/null @@ -1,1110 +0,0 @@ -registerService(new NullLogger(), LoggerInterface::class . '.quiet'); - - $this->setUpAssetStore(); - - /** @var File[] $files */ - $files = File::get()->exclude('ClassName', Folder::class); - foreach ($files as $file) { - $file->setFromString($file->getFilename(), $file->getFilename()); - $file->write(); - } - } - - protected function setUpAssetStore() - { - TestAssetStore::activate('NormaliseAccessMigrationHelperTest/assets'); - } - - protected function tearDown(): void - { - TestAssetStore::reset(); - Filesystem::removeFolder($this->getBasePath()); - parent::tearDown(); - } - - /** - * This test is not testing the helper. It is testing that our asset store set up is behaving as expected. - */ - public function testSanityCheck() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - $publicFs = $store->getPublicFilesystem(); - $protectedFs = $store->getProtectedFilesystem(); - - $naturalPath = $file->getFilename(); - $hashPath = sprintf( - '%s/%s', - substr($file->getHash() ?? '', 0, 10), - $file->getFilename() - ); - - $this->assertFalse($publicFs->has($naturalPath)); - $this->assertFalse($publicFs->has($hashPath)); - $this->assertFalse($protectedFs->has($naturalPath)); - $this->assertTrue($protectedFs->has($hashPath)); - - $file->publishSingle(); - $this->assertTrue($publicFs->has($naturalPath)); - $this->assertFalse($publicFs->has($hashPath)); - $this->assertFalse($protectedFs->has($naturalPath)); - $this->assertFalse($protectedFs->has($hashPath)); - - $file->doArchive(); - $this->assertFalse($publicFs->has($naturalPath)); - $this->assertFalse($publicFs->has($hashPath)); - $this->assertFalse($protectedFs->has($naturalPath)); - $this->assertFalse($protectedFs->has($hashPath)); - } - - private function getHelper() - { - return new Helper('/assets/NormaliseAccessMigrationHelperTest/'); - } - - public function testNeedToMoveWithFolder() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $this->expectException(\InvalidArgumentException::class); - /** @var File $file */ - $folder = Folder::find('Uploads'); - $helper = $this->getHelper(); - $helper->needToMove($folder); - } - - public function testNeedToMovePublishedNoRestrictionFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::ANYONE; - $file->publishSingle(); - $file->publishFile(); - - $helper = $this->getHelper(); - $action = $helper->needToMove($file); - $this->assertEmpty( - $action, - 'Published non-retricted file on public store does not require any moving' - ); - - $file->protectFile(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PUBLIC], - $action, - 'Published non-retricted file on protected store need to be move to public store' - ); - } - - public function testNeedToMovePublishedRestrictedFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->publishSingle(); - $file->publishFile(); - - $helper = $this->getHelper(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PROTECTED], - $action, - 'Published retricted file on public store need to be move to protected store' - ); - - $file->protectFile(); - $action = $helper->needToMove($file); - $this->assertEmpty( - $action, - 'Published retricted file on protected store does not require any moving' - ); - } - - public function testNeedToMoveMetaChangedOnlyNoRestrictionFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::ANYONE; - $file->publishSingle(); - - $file->Title = 'Changing the title should not affect which store the file is stored on'; - $file->write(); - - $file->publishFile(); - - $helper = $this->getHelper(); - $action = $helper->needToMove($file); - $this->assertEmpty( - $action, - 'Published non-retricted file on public store with metadata draft change does not need moving' - ); - - $file->protectFile(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PUBLIC], - $action, - 'Published non-retricted file on protected store with metadata draft change need to be move to public' - ); - } - - public function testNeedToMoveMetaChangedOnlyRestrictedFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->publishSingle(); - - $file->Title = 'Changing the title should not affect which store the file is stored on'; - $file->write(); - - $file->publishFile(); - - $helper = $this->getHelper(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PROTECTED], - $action, - 'Published retricted file on public store with draft metadata changes need to move to protected store' - ); - - $file->protectFile(); - $action = $helper->needToMove($file); - $this->assertEmpty( - $action, - 'Published retricted file on protected store with draft metadata changes does not need moving' - ); - } - - public function testNeedToMoveDraftNoRestrictionFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->doUnpublish(); - $file->CanViewType = InheritedPermissions::ANYONE; - $file->write(); - $file->publishFile(); - - $helper = $this->getHelper(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED], - $action, - 'draft non-restricted file on public store need to move to protected store' - ); - - $file->protectFile(); - $action = $helper->needToMove($file); - $this->assertEmpty( - $action, - 'draft non-restricted file on protected store do not need to be moved' - ); - } - - public function testNeedToMoveDraftRestrictedFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->doUnpublish(); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->write(); - $file->publishFile(); - - $helper = $this->getHelper(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED], - $action, - 'Draft restricted file on public store need to be moved to protected strore' - ); - - $file->protectFile(); - $action = $helper->needToMove($file); - $this->assertEmpty( - $action, - 'Draft restricted file on public store don\'t need to move' - ); - } - - public function testNeedToMoveUnrestrictedMixedDraftLiveFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::ANYONE; - $file->publishSingle(); - - $file->setFromString('Draf file 1', $file->getFilename()); - $file->write(); - /** @var File $liveFile */ - $liveFile = Versioned::get_by_stage($file->ClassName, Versioned::LIVE)->byID($file->ID); - - $helper = $this->getHelper(); - - $file->protectFile(); - $liveFile->protectFile(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PUBLIC], - $action, - 'Non-restricted published file on protected store with draft on protected store need to move to public' - ); - - $liveFile->publishFile(); - $action = $helper->needToMove($file); - $this->assertEmpty( - $action, - 'Non-restricted published file on public store with draft on protected store do not need to move' - ); - - $liveFile->protectFile(); - $file->publishFile(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PUBLIC, Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED], - $action, - 'Non-restricted published file on protected store with draft on public store need to move ' . - 'live file for public and draft file to protected' - ); - } - - public function testNeedToMoveRestrictedMixedDraftLiveFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->publishSingle(); - - $file->setFromString('Draf file 1', $file->getFilename()); - $file->write(); - /** @var File $liveFile */ - $liveFile = Versioned::get_by_stage($file->ClassName, Versioned::LIVE)->byID($file->ID); - - $helper = $this->getHelper(); - - $file->protectFile(); - $liveFile->protectFile(); - $action = $helper->needToMove($file); - $this->assertEmpty( - $action, - 'Restricted published file on protected store with draft on protected store do not need to move' - ); - - $liveFile->publishFile(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PROTECTED], - $action, - 'Restricted published file on public store with draft on protected store need to move to protected' - ); - - $liveFile->protectFile(); - $file->publishFile(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED], - $action, - 'Restricted published file on protected store with draft on public store need to move draft to protected' - ); - } - - public function testNeedToMoveRestrictedLiveFileUnrestrictedDraft() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->publishSingle(); - $file->CanViewType = InheritedPermissions::ANYONE; - $file->write(); - - $helper = $this->getHelper(); - - $file->protectFile(); - $action = $helper->needToMove($file); - $this->assertEmpty( - $action, - 'Restricted published file on protected store with unrestricted draft do not need to be moved' - ); - - $file->publishFile(); - $action = $helper->needToMove($file); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PROTECTED], - $action, - 'Restricted published file on public store with unrestricted draft need to move to protected store' - ); - } - - /** - * @note Live files get their permissions from the draft file. This is probably a bug, but we'll have to - * look at it later. - */ - public function testNeedToMoveUnrestrictedLiveFileRestrictedDraft() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::ANYONE; - $file->publishSingle(); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->write(); - - $helper = $this->getHelper(); - - $file->protectFile(); - $this->assertEmpty( - $helper->needToMove($file), - 'Unrestricted published file on protected store with restricted draft do not need to move' - ); - - $file->publishFile(); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PROTECTED], - $helper->needToMove($file), - 'Unrestricted published file on public store with restricted draft need to move to protected store' - ); - } - - /** - * @note Live files get their permissions from the draft file. This is probably a bug, but we'll have to - * look at it later. - */ - public function testNeedToMoveRestrictedLiveUnrestrictedDraft() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->publishSingle(); - $file->CanViewType = InheritedPermissions::ANYONE; - $file->write(); - - $helper = $this->getHelper(); - - $file->protectFile(); - $this->assertEmpty( - $helper->needToMove($file), - 'Restricted published file on protected store with unrestricted draft do not need to move' - ); - - $file->publishFile(); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PROTECTED], - $helper->needToMove($file), - 'Restricted published file on public store with unrestricted draft need to move to protected store' - ); - } - - public function testNeedToMoveFileInProtectedFolder() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'secret'); - $file->doUnpublish(); - - $helper = $this->getHelper(); - - $file->protectFile(); - $this->assertEmpty( - $helper->needToMove($file), - 'Folder-restricted draft file on protected store do not need to move' - ); - - $file->publishFile(); - $this->assertEquals( - [Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED], - $helper->needToMove($file), - 'Folder-restricted draft file on public store need to move to protected' - ); - - $file->publishSingle(); - $file->protectFile(); - $this->assertEmpty( - $helper->needToMove($file), - 'Folder-restricted live file on protected store do not need to move' - ); - - $file->publishFile(); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PROTECTED], - $helper->needToMove($file), - 'Folder-restricted live file on public store need to move to protected store' - ); - } - - public function testNeedToMoveRenamedFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::ANYONE; - $file->publishSingle(); - $file->setFilename('updated-file-name.txt'); - $file->write(); - $helper = $this->getHelper(); - - $file->publishFile(); - $this->assertEmpty( - $helper->needToMove($file), - 'Live file on public store with rename draft file does not need to be move' - ); - - $file->protectFile(); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PUBLIC], - $helper->needToMove($file), - 'Live file on protected store with rename draft file does need to be moved to public' - ); - } - - public function testNeedToMoveReplacedRenamedFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::ANYONE; - $file->publishSingle(); - $liveVersion = $file->Version; - $file->setFromString('New file with new name', 'updated-file-name.txt'); - $file->write(); - - /** @var File $liveFile */ - $liveFile = Versioned::get_version(File::class, $file->ID, $liveVersion); - - $helper = $this->getHelper(); - - $file->protectFile(); - $liveFile->publishFile(); - $this->assertEmpty( - $helper->needToMove($file), - 'Live file on public store with renamed-replaced draft on protected store ' . - 'do not need to move' - ); - - $liveFile->protectFile(); - $file->publishFile(); - $this->assertEquals( - [Versioned::LIVE => AssetStore::VISIBILITY_PUBLIC, Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED], - $helper->needToMove($file), - 'Live file on protected store with renamed-replaced draft on public store ' . - 'need to be life file to public and draft file to protected' - ); - } - - public function testFindBadFilesWithProtectedDraft() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - $file->protectFile(); - } - $helper = $this->getHelper(); - $this->assertEmpty( - $helper->findBadFiles(), - 'All files are in draft and protected. There\'s nothing to do' - ); - } - - public function testFindBadFilesWithPublishedDraft() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - $file->publishFile(); - } - $helper = $this->getHelper(); - $this->assertEquals( - [ - $this->idFromFixture(File::class, 'file1') => - [Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED], - $this->idFromFixture(File::class, 'secret') => - [Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED], - ], - $helper->findBadFiles(), - 'All files are in draft and public. All of files need to be moved to the protected store' - ); - } - - public function testFindBadFilesWithProtectedLive() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - $file->publishSingle(); - $file->protectFile(); - } - $helper = $this->getHelper(); - $this->assertEmpty( - $helper->findBadFiles(), - 'All files are published and protected. file1 should be public, ' . - 'but the helper doesn\'t publish wrongly protected file' - ); - } - - public function testFindBadFilesWithPublishedLive() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - $file->publishSingle(); - $file->publishFile(); - } - $helper = $this->getHelper(); - - $this->assertEquals( - [ - $this->idFromFixture(File::class, 'secret') => - [Versioned::LIVE => AssetStore::VISIBILITY_PROTECTED], - ], - $helper->findBadFiles(), - 'All files are published and published. secret is mark as bad because it should be protected' - ); - } - - public function testFindBadFilesWithMultiPublishedVersions() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - $file->publishSingle(); - $liveFile = Versioned::get_version(File::class, $file->ID, $file->Version); - $file->protectFile(); - $file->setFromString($file->getFilename() . ' draft content', $file->getFilename()); - $file->write(); - $liveFile->protectFile(); - $file->publishFile(); - } - $helper = $this->getHelper(); - - $this->assertEquals( - [ - $this->idFromFixture(File::class, 'file1') => - [ - Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED - ], - $this->idFromFixture(File::class, 'secret') => - [Versioned::DRAFT => AssetStore::VISIBILITY_PROTECTED], - ], - $helper->findBadFiles(), - 'When files have different draft version, the draft and live version should be check individually' - ); - } - - public function testNeedToMoveNonExistentFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $this->expectException(\LogicException::class); - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->publishSingle(); - $file->deleteFile(); - - $helper = $this->getHelper(); - - $action = $helper->needToMove($file); - } - - public function testRun() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - $file->publishFile(); - } - $helper = $this->getHelper(); - - $this->assertEquals( - [ - 'total' => 2, - 'success' => 2, - 'fail' => 0 - ], - $helper->run(), - '2 files need to be protected' - ); - - $this->assertEquals( - [ - 'total' => 0, - 'success' => 0, - 'fail' => 0 - ], - $helper->run(), - 'All files have already been protected' - ); - } - - /** - * The point of this test is to make sure the chunking logic works as expected - */ - public function testRunWithLotsOfFiles() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - // Create a two hundred good files - for ($i = 0; $i < 200; $i++) { - $file = new File(); - $file->setFromString("good file $i", "good$i.txt"); - $file->write(); - } - - // Create a two hundred bad files with draft in the public store - for ($i = 0; $i < 201; $i++) { - $file = new File(); - $file->setFromString("bad file $i", "bad$i.txt"); - $file->write(); - $file->publishFile(); - } - - // Create a two hundred fail files that will throw warning - for ($i = 0; $i < 202; $i++) { - $file = new File(); - $file->setFromString("fail file $i", "fail$i.txt"); - $file->write(); - $file->deleteFile(); - } - - $helper = $this->getHelper(); - - $this->assertEquals( - [ - 'total' => 201, - 'success' => 201, - 'fail' => 202 - ], - $helper->run(), - 'When looping over a list of files greater than limit, all files should be prcessed' - ); - - $this->assertEquals( - [ - 'total' => 0, - 'success' => 0, - 'fail' => 202 - ], - $helper->run(), - 'When running the helper twice in a row, all files that can be fix have already been fixed.' - ); - } - - public function testFixWithProtectedDraftFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->protectFile(); - - $helper = $this->getHelper(); - $helper->fix($file); - $this->assertVisibility( - AssetStore::VISIBILITY_PROTECTED, - $file, - 'Protected draft file is protected after fix' - ); - } - - public function testFixWithPublicDraftFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->publishFile(); - - $helper = $this->getHelper(); - $helper->fix($file); - $this->assertVisibility( - AssetStore::VISIBILITY_PROTECTED, - $file, - 'Public draft file is protected after fix' - ); - } - - public function testFixWithPublicLiveFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->publishSingle(); - $file->publishFile(); - - $helper = $this->getHelper(); - $helper->fix($file); - $this->assertVisibility( - AssetStore::VISIBILITY_PUBLIC, - $file, - 'Public live file is public after fix' - ); - } - - public function testFixWithProtectedLiveFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->publishSingle(); - $file->protectFile(); - - $helper = $this->getHelper(); - $helper->fix($file); - $this->assertVisibility( - AssetStore::VISIBILITY_PUBLIC, - $file, - 'Protected live file is public after fix' - ); - } - - public function testFixWithPublicRestrictedLiveFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->publishSingle(); - $file->publishFile(); - - $helper = $this->getHelper(); - $helper->fix($file); - $this->assertVisibility( - AssetStore::VISIBILITY_PROTECTED, - $file, - 'Public restricted live file is protected after fix' - ); - } - - public function testFixWithProtectedRestrictedLiveFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->CanViewType = InheritedPermissions::LOGGED_IN_USERS; - $file->publishSingle(); - $file->protectFile(); - - $helper = $this->getHelper(); - $helper->fix($file); - $this->assertVisibility( - AssetStore::VISIBILITY_PROTECTED, - $file, - 'Protected restricted live file is protected after fix' - ); - } - - public function testFixWithUpdatedDraftMetadataFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->publishSingle(); - $liveVersionID = $file->Version; - $file->Title = 'new title'; - $file->write(); - - $helper = $this->getHelper(); - $helper->fix($file); - $this->assertVisibility( - AssetStore::VISIBILITY_PUBLIC, - $file, - 'Draft meta data changes do not affect visibility of live file' - ); - - $this->assertVisibility( - AssetStore::VISIBILITY_PUBLIC, - Versioned::get_version(File::class, $file->ID, $liveVersionID), - 'Live file is still public even if there\'s a draft metadata change' - ); - } - - public function testFixWithUpdatedDraftFile() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - $file->publishSingle(); - $liveVersionID = $file->Version; - - $file->setFromString('Updated content', 'newfile1.txt'); - $file->write(); - - /** @var File $liveFile */ - $liveFile = Versioned::get_version(File::class, $file->ID, $liveVersionID); - $liveFile->protectFile(); - $file->publishFile(); - - $helper = $this->getHelper(); - $helper->fix($file); - $this->assertVisibility( - AssetStore::VISIBILITY_PROTECTED, - $file, - '' - ); - - $this->assertVisibility( - AssetStore::VISIBILITY_PUBLIC, - $liveFile, - '' - ); - } - - public function testFixWithImageVariants() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - $public = $store->getPublicFilesystem(); - $protected = $store->getProtectedFilesystem(); - $variantFilename = 'root__FillWzEwMCwxMDBd.png'; - - $img = new Image(); - $img->setFromLocalFile(__DIR__ . '/../../ImageTest/test-image.png', 'root.png'); - $img->write(); - $img->CMSThumbnail()->getURL(); - - $hashPath = sprintf('%s/%s', substr($img->getHash() ?? '', 0, 10), $variantFilename); - - $this->assertTrue($protected->has($hashPath)); - - $img->publishFile(); - $this->assertFalse($protected->has($hashPath)); - $this->assertTrue($public->has($variantFilename)); - - $helper = $this->getHelper(); - $helper->fix($img); - - $this->assertTrue($protected->has($hashPath)); - $this->assertFalse($public->has($variantFilename)); - } - - private function assertVisibility($expected, File $file, $message = '') - { - $this->assertEquals($expected, $file->getVisibility(), $message); - } - - - public function truncatingFolderDataProvider() - { - return [ - 'root files' => [ - ['bad.txt' => true, 'good.txt' => false], - ['good.txt'], - ['bad.txt'] - ], - 'bad root files only' => [ - ['bad.txt' => true], - [], - ['bad.txt'], - ], - 'files in folder' => [ - [ - 'good/good.txt' => false, - 'good/bad.txt' => true, - 'bad/bad.txt' => true, - ], - ['good/good.txt'], - ['bad', 'good/bad.txt'], - ], - 'bad files in subfolder' => [ - [ - 'dir/good.txt' => false, - 'dir/bad/bad.txt' => true, - ], - ['dir/good.txt'], - ['dir/bad'], - ], - 'good files in subfolder' => [ - [ - 'folder/bad.txt' => true, - 'folder/good/good.txt' => false, - ], - ['folder/good/good.txt'], - ['folder/bad.txt'], - ], - 'bad files inside deep folder' => [ - ['deeply/bad/file.txt' => true], - [], - ['deeply'], - ], - 'bad files in subfolder with bad file in parent' => [ - [ - 'dir/good.txt' => false, - 'dir/bad.txt' => true, - 'dir/bad/bad.txt' => true, - ], - ['dir/good.txt'], - ['dir/bad', 'dir/bad.txt'], - ], - ]; - } - - /** - * @dataProvider truncatingFolderDataProvider - */ - public function testFolderTruncating($filePaths, $expected, $unexpected) - { - // Removing existing fixtures - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - $file->delete(); - } - - // Create a folder structure - foreach ($filePaths as $filePath => $broken) { - $file = new File(); - $file->setFromString('dummy file', $filePath); - $file->write(); - if (!$broken) { - $file->publishSingle(); - } - $file->publishFile(); - } - - /** @var \League\Flysystem\Filesystem $fs */ - $fs = Injector::inst()->get(AssetStore::class)->getPublicFilesystem(); - - // Assert that the expect files exists prior to the task running - foreach ($expected as $expectedPath) { - $this->assertTrue($fs->has($expectedPath), sprintf('%s exists', $expectedPath)); - } - // Assert that the unexpect files exists prior to the task running - foreach ($unexpected as $unexpectedPath) { - $this->assertTrue($fs->has($unexpectedPath), sprintf('%s does not exist', $unexpectedPath)); - } - - // Fix broken file - $helper = $this->getHelper(); - $helper->run(); - - // Assert existence of file/folder - foreach ($expected as $expectedPath) { - $this->assertTrue($fs->has($expectedPath), sprintf('%s exists', $expectedPath)); - } - // Assert non-existence of file/folder - foreach ($unexpected as $unexpectedPath) { - $this->assertFalse($fs->has($unexpectedPath), sprintf('%s does not exist', $unexpectedPath)); - } - } -} diff --git a/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperTest.yml b/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperTest.yml deleted file mode 100644 index a3003300..00000000 --- a/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperTest.yml +++ /dev/null @@ -1,22 +0,0 @@ -SilverStripe\Security\Group: - file-master: - Title: File Master - -SilverStripe\Assets\Folder: - parent: - Name: Uploads - restricted: - Name: Restricted - FileFilename: Restricted - CanViewType: OnlyTheseUsers - ViewerGroups: - - =>SilverStripe\Security\Group.file-master -SilverStripe\Assets\File: - file1: - FileFilename: file1.txt - Name: file1.txt - secret: - FileFilename: Restricted/secret.txt - Name: file1.txt - CanViewType: Inherit - Parent: =>SilverStripe\Assets\Folder.restricted diff --git a/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperWithHashPathTest.php b/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperWithHashPathTest.php deleted file mode 100644 index c35afe20..00000000 --- a/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperWithHashPathTest.php +++ /dev/null @@ -1,181 +0,0 @@ -get(FileResolutionStrategy::class . '.public'); - - $hashHelper = new HashFileIDHelper(); - $strategy->setDefaultFileIDHelper($hashHelper); - } - - /** - * This test is not testing the helper. It is testing that our asset store set up is behaving as expected. - */ - public function testSanityCheck() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - $publicFs = $store->getPublicFilesystem(); - $protectedFs = $store->getProtectedFilesystem(); - - $naturalPath = $file->getFilename(); - $hashPath = sprintf( - '%s/%s', - substr($file->getHash() ?? '', 0, 10), - $file->getFilename() - ); - - $this->assertFalse($publicFs->has($naturalPath)); - $this->assertFalse($publicFs->has($hashPath)); - $this->assertFalse($protectedFs->has($naturalPath)); - $this->assertTrue($protectedFs->has($hashPath)); - - $file->publishSingle(); - $this->assertFalse($publicFs->has($naturalPath)); - $this->assertTrue($publicFs->has($hashPath)); - $this->assertFalse($protectedFs->has($naturalPath)); - $this->assertFalse($protectedFs->has($hashPath)); - - $file->doArchive(); - $this->assertFalse($publicFs->has($naturalPath)); - $this->assertFalse($publicFs->has($hashPath)); - $this->assertFalse($protectedFs->has($naturalPath)); - $this->assertFalse($protectedFs->has($hashPath)); - } - - public function testFixWithImageVariants() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - $public = $store->getPublicFilesystem(); - $protected = $store->getProtectedFilesystem(); - $variantFilename = 'root__FillWzEwMCwxMDBd.png'; - - $img = new Image(); - $img->setFromLocalFile(__DIR__ . '/../../ImageTest/test-image.png', 'root.png'); - $img->write(); - $img->CMSThumbnail()->getURL(); - - $hashPath = sprintf('%s/%s', substr($img->getHash() ?? '', 0, 10), $variantFilename); - - $this->assertTrue($protected->has($hashPath)); - - $img->publishFile(); - $this->assertFalse($protected->has($hashPath)); - $this->assertTrue($public->has($hashPath)); - - $helper = Helper::create(); - $helper->fix($img); - - $this->assertTrue($protected->has($hashPath)); - $this->assertFalse($public->has($hashPath)); - } - - public function truncatingFolderDataProvider() - { - $hash = substr(sha1('dummy file'), 0, 10); - - return [ - 'root files' => [ - ['bad.txt' => true, 'good.txt' => false], - ["$hash/good.txt"], - ["$hash/bad.txt"] - ], - 'bad root files only' => [ - ['bad.txt' => true], - [], - ["$hash/bad.txt"], - ], - 'files in folder' => [ - [ - 'good/good.txt' => false, - 'good/bad.txt' => true, - 'bad/bad.txt' => true, - ], - ["good/$hash/good.txt"], - ['bad', "good/$hash/bad.txt"], - ], - 'bad files in subfolder' => [ - [ - 'dir/good.txt' => false, - 'dir/bad/bad.txt' => true, - ], - ["dir/$hash/good.txt"], - ['dir/bad'], - ], - 'good files in subfolder' => [ - [ - 'folder/bad.txt' => true, - 'folder/good/good.txt' => false, - ], - ["folder/good/$hash/good.txt"], - ["folder/$hash/bad.txt"], - ], - 'bad files inside deep folder' => [ - ['deeply/bad/file.txt' => true], - [], - ['deeply'], - ], - 'bad files in subfolder with bad file in parent' => [ - [ - 'dir/good.txt' => false, - 'dir/bad.txt' => true, - 'dir/bad/bad.txt' => true, - ], - ["dir/$hash/good.txt"], - ['dir/bad', "dir/$hash/bad.txt"], - ], - ]; - } -} diff --git a/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperWithKeepArchivedTest.php b/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperWithKeepArchivedTest.php deleted file mode 100644 index 03b210b5..00000000 --- a/tests/php/Dev/Tasks/NormaliseAccessMigrationHelperWithKeepArchivedTest.php +++ /dev/null @@ -1,89 +0,0 @@ -set('keep_archived_assets', true); - } - - /** - * This test is not testing the helper. It is testing that our asset store set up is behaving as expected. - */ - public function testSanityCheck() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var File $file */ - $file = $this->objFromFixture(File::class, 'file1'); - - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - $publicFs = $store->getPublicFilesystem(); - $protectedFs = $store->getProtectedFilesystem(); - - $naturalPath = $file->getFilename(); - $hashPath = sprintf( - '%s/%s', - substr($file->getHash() ?? '', 0, 10), - $file->getFilename() - ); - - $this->assertFalse($publicFs->has($naturalPath)); - $this->assertFalse($publicFs->has($hashPath)); - $this->assertFalse($protectedFs->has($naturalPath)); - $this->assertTrue($protectedFs->has($hashPath)); - - $file->publishSingle(); - $this->assertTrue($publicFs->has($naturalPath)); - $this->assertFalse($publicFs->has($hashPath)); - $this->assertFalse($protectedFs->has($naturalPath)); - $this->assertFalse($protectedFs->has($hashPath)); - - $file->doArchive(); - $this->assertFalse($publicFs->has($naturalPath)); - $this->assertFalse($publicFs->has($hashPath)); - $this->assertFalse($protectedFs->has($naturalPath)); - $this->assertTrue($protectedFs->has($hashPath)); - } -} diff --git a/tests/php/Dev/Tasks/SS4CrazyFileMigrationHelperTest.php b/tests/php/Dev/Tasks/SS4CrazyFileMigrationHelperTest.php deleted file mode 100644 index a642d5a9..00000000 --- a/tests/php/Dev/Tasks/SS4CrazyFileMigrationHelperTest.php +++ /dev/null @@ -1,71 +0,0 @@ -get(AssetStore::class); - - $hashHelper = new HashFileIDHelper(); - $legacyHelper = new LegacyFileIDHelper(); - - $protected = FileIDHelperResolutionStrategy::create(); - $protected->setVersionedStage(Versioned::DRAFT); - $protected->setDefaultFileIDHelper($hashHelper); - $protected->setResolutionFileIDHelpers([$hashHelper]); - - $store->setProtectedResolutionStrategy($protected); - - $public = FileIDHelperResolutionStrategy::create(); - $public->setVersionedStage(Versioned::LIVE); - $public->setDefaultFileIDHelper($legacyHelper); - $public->setResolutionFileIDHelpers([$legacyHelper]); - - $store->setPublicResolutionStrategy($public); - } - - protected function defineDestinationStrategy() - { - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - - $hashHelper = new HashFileIDHelper(); - $naturalPath = new NaturalFileIDHelper(); - $legacyHelper = new LegacyFileIDHelper(); - - $protected = FileIDHelperResolutionStrategy::create(); - $protected->setVersionedStage(Versioned::DRAFT); - $protected->setDefaultFileIDHelper($hashHelper); - $protected->setResolutionFileIDHelpers([$hashHelper]); - - $store->setProtectedResolutionStrategy($protected); - - $public = FileIDHelperResolutionStrategy::create(); - $public->setVersionedStage(Versioned::LIVE); - $public->setDefaultFileIDHelper($hashHelper); - $public->setResolutionFileIDHelpers([$hashHelper, $naturalPath, $legacyHelper]); - - $store->setPublicResolutionStrategy($public); - } -} diff --git a/tests/php/Dev/Tasks/SS4FileMigrationHelperTest.php b/tests/php/Dev/Tasks/SS4FileMigrationHelperTest.php deleted file mode 100644 index da63dfb4..00000000 --- a/tests/php/Dev/Tasks/SS4FileMigrationHelperTest.php +++ /dev/null @@ -1,315 +0,0 @@ - [ - Extension::class, - ] - ]; - - protected function setUp(): void - { - Config::nest(); // additional nesting here necessary - Config::modify()->merge(File::class, 'migrate_legacy_file', false); - - // Set backend root to /FileMigrationHelperTest/assets - TestAssetStore::activate('FileMigrationHelperTest'); - $this->defineOriginStrategy(); - parent::setUp(); - - // Ensure that each file has a local record file in this new assets base - /** @var File $file */ - foreach (File::get()->filter('ClassName', File::class) as $file) { - $filename = $file->getFilename(); - - // Create an archive version of the file - DBDatetime::set_mock_now('2000-01-01 11:00:00'); - $file->setFromString('Archived content of ' . $filename, $filename); - $file->write(); - $file->publishSingle(); - DBDatetime::clear_mock_now(); - - // Publish a version of the file - $file->setFromString('Published content of ' . $filename, $filename); - $file->write(); - $file->publishSingle(Versioned::DRAFT, Versioned::LIVE); - - // Create a draft of the file - $file->setFromString('Draft content of ' . $filename, $filename); - $file->write(); - } - - // Let's create some variants for our images - /** @var Image $image */ - foreach (Image::get() as $image) { - $filename = $image->getFilename(); - - // Create an archive version of our image with a thumbnail - DBDatetime::set_mock_now('2000-01-01 11:00:00'); - $stream = $this->generateImage('Archived', $filename)->stream($image->getExtension()); - $image->setFromStream(StreamWrapper::getResource($stream), $filename); - $image->write(); - $image->CMSThumbnail(); - $image->publishSingle(); - DBDatetime::clear_mock_now(); - - // Publish a live version of our image with a thumbnail - $stream = $this->generateImage('Published', $filename)->stream($image->getExtension()); - $image->setFromStream(StreamWrapper::getResource($stream), $filename); - $image->write(); - $image->CMSThumbnail(); - $image->publishSingle(); - - // Create a draft version of our images with a thumbnail - $stream = $this->generateImage('Draft', $filename)->stream($image->getExtension()); - $image->setFromStream(StreamWrapper::getResource($stream), $filename); - $image->CMSThumbnail(); - $image->write(); - } - - $this->defineDestinationStrategy(); - } - - /** - * Generate a placeholder image - * @param string $targetedStage - * @param string $filename - * @return \Intervention\Image\Image - */ - private function generateImage($targetedStage, $filename) - { - /** @var ImageManager $imageManager */ - $imageManager = Injector::inst()->create(ImageManager::class); - return $imageManager - ->canvas(400, 300, '#142237') - ->text($targetedStage, 20, 170, function (AbstractFont $font) { - $font->color('#44C8F5'); - $font->align(''); - $font->valign(''); - })->text($filename, 20, 185, function (AbstractFont $font) { - $font->color('#ffffff'); - $font->align(''); - $font->valign(''); - })->rectangle(20, 200, 100, 202, function (AbstractShape $shape) { - $shape->background('#DA1052'); - }); - } - - /** - * Called by set up before creating all the fixture entries. Defines the original startegies for the assets store. - */ - protected function defineOriginStrategy() - { - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - - $hashHelper = new HashFileIDHelper(); - - $protected = FileIDHelperResolutionStrategy::create(); - $protected->setVersionedStage(Versioned::DRAFT); - $protected->setDefaultFileIDHelper($hashHelper); - $protected->setResolutionFileIDHelpers([$hashHelper]); - - $store->setProtectedResolutionStrategy($protected); - - $public = FileIDHelperResolutionStrategy::create(); - $public->setVersionedStage(Versioned::LIVE); - $public->setDefaultFileIDHelper($hashHelper); - $public->setResolutionFileIDHelpers([$hashHelper]); - - $store->setPublicResolutionStrategy($public); - } - - /** - * Called by set up after creating all the fixture entries. Defines the targeted strategies that the - * FileMigrationHelper should move the files to. - */ - protected function defineDestinationStrategy() - { - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - - $hashHelper = new HashFileIDHelper(); - $naturalHelper = new NaturalFileIDHelper(); - - $protected = FileIDHelperResolutionStrategy::create(); - $protected->setVersionedStage(Versioned::DRAFT); - $protected->setDefaultFileIDHelper($hashHelper); - $protected->setResolutionFileIDHelpers([$hashHelper, $naturalHelper]); - - $store->setProtectedResolutionStrategy($protected); - - $public = FileIDHelperResolutionStrategy::create(); - $public->setVersionedStage(Versioned::LIVE); - $public->setDefaultFileIDHelper($naturalHelper); - $public->setResolutionFileIDHelpers([$hashHelper, $naturalHelper]); - - $store->setPublicResolutionStrategy($public); - } - - protected function tearDown(): void - { - TestAssetStore::reset(); - parent::tearDown(); - Config::unnest(); - } - - public function testMigration() - { - $helper = new FileMigrationHelper(); - $result = $helper->run(TestAssetStore::base_path()); - - // Let's look at our draft files - Versioned::withVersionedMode(function () { - Versioned::set_stage(Versioned::DRAFT); - foreach (File::get()->filter('ClassName', File::class) as $file) { - $this->assertFileAt($file, AssetStore::VISIBILITY_PROTECTED, 'Draft'); - } - - foreach (Image::get() as $image) { - $this->assertImageAt($image, AssetStore::VISIBILITY_PROTECTED, 'Draft'); - } - }); - - // Let's look at our live files - Versioned::withVersionedMode(function () { - Versioned::set_stage(Versioned::LIVE); - - // There's one file with restricted views, the published version of this file will be protected - $restrictedFileID = $this->idFromFixture(File::class, 'restrictedViewFolder-file4'); - $this->lookAtRestrictedFile($restrictedFileID); - - /** @var File $file */ - foreach (File::get()->filter('ClassName', File::class)->exclude('ID', $restrictedFileID) as $file) { - $this->assertFileAt($file, AssetStore::VISIBILITY_PUBLIC, 'Published'); - } - - foreach (Image::get() as $image) { - $this->assertImageAt($image, AssetStore::VISIBILITY_PUBLIC, 'Published'); - } - }); - } - - /** - * Test that this restricted file is protected. This test is in its own method so that transition where this - * scenario can not exist can override it. - * @param $restrictedFileID - */ - protected function lookAtRestrictedFile($restrictedFileID) - { - $restrictedFile = File::get()->byID($restrictedFileID); - $this->assertFileAt($restrictedFile, AssetStore::VISIBILITY_PROTECTED, 'Published'); - } - - /** - * Convenience method to group a bunch of assertions about a regular files - * @param File $file - * @param string $visibility Expected visibility of the file - * @param string $stage Stage that we are testing, will appear in some error messages and in the expected content - */ - protected function assertFileAt(File $file, $visibility, $stage) - { - $ucVisibility = ucfirst($visibility ?? ''); - $filename = $file->getFilename(); - $hash = $file->getHash(); - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - /** @var Filesystem $fs */ - $fs = call_user_func([$store, "get{$ucVisibility}Filesystem"]); - /** @var FileResolutionStrategy $strategy */ - $strategy = call_user_func([$store, "get{$ucVisibility}ResolutionStrategy"]); - - $this->assertEquals( - $visibility, - $store->getVisibility($filename, $hash), - sprintf('%s version of %s should be %s', $stage, $filename, $visibility) - ); - $expectedURL = $strategy->buildFileID(new ParsedFileID($filename, $hash)); - $this->assertTrue( - $fs->has($expectedURL), - sprintf('%s version of %s should be on %s store under %s', $stage, $filename, $visibility, $expectedURL) - ); - $this->assertEquals( - sprintf('%s content of %s', $stage, $filename), - $fs->read($expectedURL), - sprintf('%s version of %s on %s store has wrong content', $stage, $filename, $visibility) - ); - } - - /** - * Convenience method to group a bunch of assertions about an image - * @param File $file - * @param string $visibility Expected visibility of the file - * @param string $stage Stage that we are testing, will appear in some error messages - */ - protected function assertImageAt(Image $file, $visibility, $stage) - { - $ucVisibility = ucfirst($visibility ?? ''); - $filename = $file->getFilename(); - $hash = $file->getHash(); - $pfid = new ParsedFileID($filename, $hash); - - /** @var FlysystemAssetStore $store */ - $store = Injector::inst()->get(AssetStore::class); - - /** @var Filesystem $fs */ - $fs = call_user_func([$store, "get{$ucVisibility}Filesystem"]); - /** @var FileResolutionStrategy $strategy */ - $strategy = call_user_func([$store, "get{$ucVisibility}ResolutionStrategy"]); - - $this->assertEquals( - $visibility, - $store->getVisibility($filename, $hash), - sprintf('%s version of %s should be %s', $stage, $filename, $visibility) - ); - - $expectedURL = $strategy->buildFileID($pfid); - $this->assertTrue( - $fs->has($expectedURL), - sprintf('%s version of %s should be on %s store under %s', $stage, $filename, $visibility, $expectedURL) - ); - $expectedURL = $strategy->buildFileID($pfid->setVariant('FillWzEwMCwxMDBd')); - $this->assertTrue( - $fs->has($expectedURL), - sprintf('%s thumbnail of %s should be on %s store under %s', $stage, $filename, $visibility, $expectedURL) - ); - } -} diff --git a/tests/php/Dev/Tasks/SS4KeepArchivedFileMigrationHelperTest.php b/tests/php/Dev/Tasks/SS4KeepArchivedFileMigrationHelperTest.php deleted file mode 100644 index f83bda21..00000000 --- a/tests/php/Dev/Tasks/SS4KeepArchivedFileMigrationHelperTest.php +++ /dev/null @@ -1,43 +0,0 @@ -filter('ClassName', File::class) as $file) { - $this->assertFileAt($file, AssetStore::VISIBILITY_PROTECTED, 'Archived'); - } - - foreach (Image::get() as $image) { - $this->assertImageAt($image, AssetStore::VISIBILITY_PROTECTED, 'Archived'); - } - }); - } - - protected function defineOriginStrategy() - { - parent::defineOriginStrategy(); - - File::config()->set('keep_archived_assets', true); - } -} diff --git a/tests/php/Dev/Tasks/SS4LegacyFileMigrationHelperTest.php b/tests/php/Dev/Tasks/SS4LegacyFileMigrationHelperTest.php deleted file mode 100644 index f0fa8015..00000000 --- a/tests/php/Dev/Tasks/SS4LegacyFileMigrationHelperTest.php +++ /dev/null @@ -1,53 +0,0 @@ -get(AssetStore::class); - - $naturalHelper = new NaturalFileIDHelper(); - - $protected = FileIDHelperResolutionStrategy::create(); - $protected->setVersionedStage(Versioned::DRAFT); - $protected->setDefaultFileIDHelper($naturalHelper); - $protected->setResolutionFileIDHelpers([$naturalHelper]); - - $store->setProtectedResolutionStrategy($protected); - - $public = FileIDHelperResolutionStrategy::create(); - $public->setVersionedStage(Versioned::LIVE); - $public->setDefaultFileIDHelper($naturalHelper); - $public->setResolutionFileIDHelpers([$naturalHelper]); - - $store->setPublicResolutionStrategy($public); - } - - protected function lookAtRestrictedFile($restrictedFileID) - { - // Legacy files names did not allow you to have a restricted file in draft and live simultanously - } - - public function testMigration() - { - // We're overriding testMigration just to make SS4LegacyFileMigrationHelperTest is in the exception - // stack if/when the test fails - parent::testMigration(); - } -} diff --git a/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.php b/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.php deleted file mode 100644 index 41a5d29d..00000000 --- a/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.php +++ /dev/null @@ -1,197 +0,0 @@ -filter('ClassName', Folder::class) as $folder) { - /** @var $folder Folder */ - $path = TestAssetStore::base_path() . '/' . $folder->generateFilename(); - Filesystem::makeFolder($path); - } - - // Ensure that each file has a local record file in this new assets base - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - /** @var $file File */ - $file->setFromString('some content', $file->generateFilename()); - $file->write(); - $file->publishFile(); - } - } - - protected function tearDown(): void - { - TestAssetStore::reset(); - Filesystem::removeFolder($this->getBasePath()); - parent::tearDown(); - } - - /** - * @dataProvider dataMigrate - */ - public function testMigrate($fixture, $htaccess, $expected) - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $helper = new SecureAssetsMigrationHelper(); - - /** @var TestAssetStore $store */ - $store = singleton(AssetStore::class); // will use TestAssetStore - $fs = $store->getPublicFilesystem(); - - /** @var Folder $folder */ - $folder = $this->objFromFixture(Folder::class, $fixture); - $path = $folder->getFilename() . '.htaccess'; - $fs->write($path, $htaccess); - $result = $helper->run($store); - - $this->assertEquals($result, $expected ? [$path] : []); - } - - public function dataMigrate() - { - $htaccess = << [ - 'protected', - $htaccess, - true - ], - 'Protected nested within unprotected, with valid htaccess' => [ - 'protected-sub', - $htaccess, - true - ], - // .htaccess files on parent folders are respected by Apache - // See SecureFileExtension->needsAccessFile() - 'Unprotected nested within protected, with valid htaccess' => [ - 'protected-inherited', - $htaccess, - false - ], - 'Unprotected' => [ - 'unprotected', - '', - false - ], - 'Protected with modified htaccess' => [ - 'protected-with-modified-htaccess', - $htaccessModified, - false - ], - ]; - } - - public function testHtaccessMatchesExact() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $htaccess = <<assertTrue($helper->htaccessMatch($htaccess)); - } - - public function testHtaccessDoesNotMatchWithAdditionsAtStart() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $htaccess = <<assertFalse($helper->htaccessMatch($htaccess)); - } - - public function testHtaccessDoesNotMatchWithAdditionsAtEnd() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $htaccess = <<assertFalse($helper->htaccessMatch($htaccess)); - } - - public function testHtaccessDoesNotMatchWithAdditionsInBetween() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $htaccess = <<assertFalse($helper->htaccessMatch($htaccess)); - } -} diff --git a/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.yml b/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.yml deleted file mode 100644 index 8c476f5d..00000000 --- a/tests/php/Dev/Tasks/SecureAssetsMigrationHelperTest.yml +++ /dev/null @@ -1,23 +0,0 @@ -SilverStripe\Assets\Folder: - parent-with-protected-sub: - Name: parent-with-protected-sub - protected-sub: - Name: protected-sub - CanViewType: LoggedInUsers - Parent: =>SilverStripe\Assets\Folder.parent-with-protected-sub - unprotected: - Name: unprotected - protected: - Name: protected - CanViewType: LoggedInUsers - protected-inherited: - Name: protected-inherited - CanViewType: Inherit - Parent: =>SilverStripe\Assets\Folder.protected - protected-with-modified-htaccess: - Name: protected-with-modified-htaccess - CanViewType: LoggedInUsers -SilverStripe\Assets\Image: - nested: - Name: nested.jpg - ParentID: =>SilverStripe\Assets\Folder.protected-sub diff --git a/tests/php/Dev/Tasks/Shortcode/HtmlObject.php b/tests/php/Dev/Tasks/Shortcode/HtmlObject.php deleted file mode 100644 index c20aca98..00000000 --- a/tests/php/Dev/Tasks/Shortcode/HtmlObject.php +++ /dev/null @@ -1,17 +0,0 @@ - 'HTMLVarchar(1024,array("shortcodes"=>true))', - 'HtmlLineNoShortCode' => 'HTMLVarchar(1024)', - 'Content' => 'HTMLText', - 'ContentNoShortCode' => 'HTMLText(array("shortcodes"=>false))' - ]; -} diff --git a/tests/php/Dev/Tasks/Shortcode/NoStage.php b/tests/php/Dev/Tasks/Shortcode/NoStage.php deleted file mode 100644 index 064be377..00000000 --- a/tests/php/Dev/Tasks/Shortcode/NoStage.php +++ /dev/null @@ -1,19 +0,0 @@ - 'HTMLText' - ]; -} diff --git a/tests/php/Dev/Tasks/Shortcode/PseudoPage.php b/tests/php/Dev/Tasks/Shortcode/PseudoPage.php deleted file mode 100644 index 6f80269f..00000000 --- a/tests/php/Dev/Tasks/Shortcode/PseudoPage.php +++ /dev/null @@ -1,21 +0,0 @@ - 'Varchar', - 'Content' => 'HTMLText', - ]; - - private static $extensions = [ - Versioned::class, - ]; - - private static $table_name = 'TagsToShortCodeHelperTest_PseudoPage'; -} diff --git a/tests/php/Dev/Tasks/Shortcode/SubHtmlObject.php b/tests/php/Dev/Tasks/Shortcode/SubHtmlObject.php deleted file mode 100644 index 486a341b..00000000 --- a/tests/php/Dev/Tasks/Shortcode/SubHtmlObject.php +++ /dev/null @@ -1,15 +0,0 @@ - DBHTMLText::class . '(["shortcodes"=>true])' - ]; -} diff --git a/tests/php/Dev/Tasks/TagsToShortcodeHelperTest.php b/tests/php/Dev/Tasks/TagsToShortcodeHelperTest.php deleted file mode 100644 index bc3b1308..00000000 --- a/tests/php/Dev/Tasks/TagsToShortcodeHelperTest.php +++ /dev/null @@ -1,543 +0,0 @@ -setupAssetStore(); - } - - private function setupAssetStore() - { - // Set backend root to /TagsToShortcodeHelperTest/assets - TestAssetStore::activate('TagsToShortcodeHelperTest/assets'); - - /** @var File $file */ - foreach (File::get()->filter('ClassName', File::class) as $file) { - $file->setFromString($file->getFilename(), $file->getFilename(), $file->getHash()); - } - - $from = __DIR__ . '/../../ImageTest/test-image-low-quality.jpg'; - foreach (Image::get() as $file) { - $file->setFromLocalFile($from, $file->getFilename(), $file->getHash()); - $file->setFromLocalFile($from, $file->getFilename(), $file->getHash(), 'ResizedImageWzY0LDY0XQ'); - } - } - - protected function tearDown(): void - { - TestAssetStore::reset(); - Filesystem::removeFolder($this->getBasePath()); - parent::tearDown(); - } - - public function testRewrite() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $tagsToShortcodeHelper = new TagsToShortcodeHelper(); - $tagsToShortcodeHelper->run(); - - /** @var PseudoPage $newPage */ - $newPage = $this->objFromFixture(PseudoPage::class, 'page1'); - $documentID = $this->idFromFixture(File::class, 'document'); - $imageID = $this->idFromFixture(Image::class, 'image1'); - - $this->assertStringContainsString( - sprintf('

    ', $documentID), - $newPage->Content - ); - - $this->assertStringContainsString( - sprintf('

    [image src="/assets/33be1b95cb/myimage.jpg" id="%d"]

    ', $imageID), - $newPage->Content - ); - - $this->assertStringContainsString( - sprintf( - '

    [image src="/assets/33be1b95cb/myimage.jpg" width="64" height="64" id="%d"]

    ', - $imageID - ), - $newPage->Content - ); - } - - public function testPublishedFileRewrite() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $document = $this->objFromFixture(File::class, 'document'); - $image = $this->objFromFixture(Image::class, 'image1'); - - $document->publishSingle(); - $image->publishSingle(); - - $this->testRewrite(); - } - - public function testLivePageRewrite() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - /** @var PseudoPage $newPage */ - $newPage = $this->objFromFixture(PseudoPage::class, 'page1'); - $newPage->publishSingle(); - - $newPage->Content = '

    Draft content

    '; - $newPage->write(); - - $tagsToShortcodeHelper = new TagsToShortcodeHelper(); - $tagsToShortcodeHelper->run(); - - /** @var PseudoPage $newPage */ - $newPageID = $newPage->ID; - $newPage = Versioned::withVersionedMode(function () use ($newPageID) { - Versioned::set_stage(Versioned::LIVE); - return PseudoPage::get()->byID($newPageID); - }); - - - $documentID = $this->idFromFixture(File::class, 'document'); - $imageID = $this->idFromFixture(Image::class, 'image1'); - - $this->assertStringContainsString( - sprintf('', $documentID), - $newPage->Content - ); - - $this->assertStringContainsString( - sprintf('

    [image src="/assets/33be1b95cb/myimage.jpg" id="%d"]

    ', $imageID), - $newPage->Content - ); - - $this->assertStringContainsString( - sprintf( - '

    [image src="/assets/33be1b95cb/myimage.jpg" width="64" height="64" id="%d"]

    ', - $imageID - ), - $newPage->Content - ); - } - - public function testRewriteRegularObject() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $tagsToShortcodeHelper = new TagsToShortcodeHelper(); - $tagsToShortcodeHelper->run(); - - $htmlObject = $this->objFromFixture(HtmlObject::class, 'htmlObject'); - - $documentID = $this->idFromFixture(File::class, 'document'); - - $this->assertEquals( - sprintf('Content Field', $documentID), - $htmlObject->Content - ); - - $this->assertEquals( - sprintf('link to file', $documentID), - $htmlObject->HtmlLine - ); - - $this->assertEquals( - 'This wont be converted', - $htmlObject->HtmlLineNoShortCode - ); - } - - public function testRewriteSubclassObject() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $tagsToShortcodeHelper = new TagsToShortcodeHelper(); - $tagsToShortcodeHelper->run(); - - $subHtmlObject = $this->objFromFixture(SubHtmlObject::class, 'subHtmlObject'); - - $documentID = $this->idFromFixture(File::class, 'document'); - $image1ID = $this->idFromFixture(Image::class, 'image1'); - - $this->assertEquals( - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" alt="SubHtmlObject Table" id="%d"]', $image1ID), - $subHtmlObject->HtmlContent - ); - - $this->assertEquals( - sprintf('Content Field', $documentID), - $subHtmlObject->Content - ); - - $this->assertEquals( - sprintf('HtmlObject Table', $documentID), - $subHtmlObject->HtmlLine - ); - } - - public function testStagelessVersionedObject() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - // This is just here to make sure that the logic for converting live content doesn't fall on its face when - // encountering a versioned object that does not support stages. - - $tagsToShortcodeHelper = new TagsToShortcodeHelper(); - $tagsToShortcodeHelper->run(); - - $stageless = $this->objFromFixture(NoStage::class, 'stageless'); - - $documentID = $this->idFromFixture(File::class, 'document'); - - $this->assertEquals( - sprintf('Stageless Versioned Object', $documentID), - $stageless->Content - ); - } - /** - * @dataProvider newContentConvertDataProvider - * @dataProvider newContentNoChangeDataProvider - * @dataProvider newContentNoDataDegradationDataProvider - * @dataProvider newContentEasyFixDataProvider - * @dataProvider newContentUnderscoreDataProvider - * @param string $input - * @param string|false $ouput If false, assume the input should be unchanged - */ - public function testNewContent($input, $output = false) - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $tagsToShortcodeHelper = new TagsToShortcodeHelper(); - $actual = $tagsToShortcodeHelper->getNewContent($input); - $this->assertEquals($output ?: $input, $actual); - } - - /** - * List of HTML string that should be converted to short code - */ - public function newContentConvertDataProvider() - { - $image1ID = 2; - $documentID = 5; - $underscoreFile = 4; - - return [ - 'link to file with starting slash' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'link to file in paragraph' => [ - '

    file link link to file

    ', - sprintf('

    file link link to file

    ', $documentID) - ], - 'link to file without starting slash' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'link with common attributes' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'link with other attributes' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'link with other attributes before href' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'link with empty attributes before href' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'link to image' => [ - 'link to file', - sprintf('link to file', $image1ID) - ], - 'link to image variant' => [ - 'link to file', - sprintf('link to file', $image1ID) - ], - 'link to image SS3.0 variant' => [ - 'link to file', - sprintf('link to file', $image1ID) - ], - 'link to hash url' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'link without closing tag' => [ - 'Link to file', - sprintf('Link to file', $documentID) - ], - - - 'simple image' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" id="%d"]', $image1ID) - ], - 'image with common attributes' => [ - 'My Image', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" alt="My Image" title="My image title" id="%d"]', $image1ID) - ], - 'image with uncommon attributes' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" xml:lang="fr" lang="fr" id="%d"]', $image1ID) - ], - 'image variant' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" id="%d"]', $image1ID)], - 'image SS3.0 variant' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" id="%d"]', $image1ID)], - 'image variant with size' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" width="100" height="133" id="%d"]', $image1ID)], - 'image variant that has not been generated yet' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" width="200" height="266" id="%d"]', $image1ID)], - 'xhtml image' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" id="%d"]', $image1ID) - ], - 'empty attribute image' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" title="" id="%d"]', $image1ID) - ], - 'image caption' => [ - '
    sam

    My caption

    ', - sprintf('
    [image class="leftAlone" src="/assets/33be1b95cb/myimage.jpg" alt="sam" width="100" height="133" id="%d"]

    My caption

    ', $image1ID) - ], - 'same image twice' => [ - str_repeat('', 2), - str_repeat(sprintf('[image src="/assets/33be1b95cb/myimage.jpg" id="%d"]', $image1ID), 2) - ], - - 'image inside file link' => [ - '
    ', - sprintf( - '[image src="/assets/33be1b95cb/myimage.jpg" id="%d"]', - $documentID, - $image1ID - ) - ], - - 'link to file with underscore' => [ - 'link to file', - sprintf('link to file', $underscoreFile) - ], - 'image with underscore' => [ - '', - sprintf('[image src="/assets/decade1980/33be1b95cb/under_score.jpg" id="%d"]', $underscoreFile) - ], - 'image inside a tag without href' => [ - '

    ', - sprintf( - '

    [image src="/assets/33be1b95cb/myimage.jpg" class="leftAlone" title="" alt="" width="600" height="400" id="%d"]

    ', - $image1ID - ) - ], - ]; - } - - /** - * List of HTML string that should be converted to short code - */ - public function newContentUnderscoreDataProvider() - { - $image1ID = 2; - $documentID = 5; - $underscoreFile = 3; - $trippleUnderscore = 4; - - return [ - 'link to file with underscore' => [ - 'link to file', - sprintf('link to file', $underscoreFile) - ], - 'image with underscore' => [ - '', - sprintf('[image src="/assets/decade80/33be1b95cb/under_score.jpg" id="%d"]', $underscoreFile) - ], - 'link to file with double underscore' => [ - 'link to file', - sprintf('link to file', $underscoreFile) - ], - 'image with double underscore' => [ - '', - sprintf('[image src="/assets/decade80/33be1b95cb/under_score.jpg" id="%d"]', $underscoreFile) - ] - ]; - } - - /** - * List of HTML string that should remain unchanged - */ - public function newContentNoChangeDataProvider() - { - $documentID = 5; - - return [ - 'external anchor' => ['External link'], - 'link already using short code' => ['link to file'], - 'link already using short code without comma' => ['link to file'], - 'link already using short code with id quote' => ['link to file'], - 'link to a site tree object' => [sprintf('Link to site tree', $documentID)], - 'link with mailto href' => ['test@example.com'], - 'link with comment in tag' => ['test@example.com'], - 'link without closing >' => [' ['assets/document.pdf'], - - 'external image' => [''], - 'image already using shortcode' => ['[image src="/assets/33be1b95cb/myimage.jpg" id="3"]'] - ]; - } - - /** - * List of HTMl string that would be converted in an ideal world, but are too unusual to analyse with regex. We're - * just testing that the content is not degraded. - */ - public function newContentNoDataDegradationDataProvider() - { - $image1ID = 2; - - return [ - 'link tag broken over several line' => ["\nlink to file \r\n \t"], - 'link to valid file with hash anchor' => ['link to file'], - 'link to valid file with Get param' => ['link to file'], - 'image with custom id' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" id="%d"]', $image1ID) - ], - ]; - } - - /** - * List of HTMl string that would be converted in an ideal world, but are too unusual to analyse with regex. We're - * just testing that the content is not degraded. - */ - public function newContentEasyFixDataProvider() - { - $image1ID = 2; - $documentID = 5; - - return [ - // Just make the regex case insensitive - 'link with uppercase tag' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'link with uppercase href' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'image with weird case' => [ - '', - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" id="%d"]', $image1ID) - ], - - // Check for single quotes as well - 'link with single quotes' => [ - 'link to file', - sprintf('link to file', $documentID) - ], - 'image with single quotes' => [ - "", - sprintf('[image src="/assets/33be1b95cb/myimage.jpg" id="%d"]', $image1ID) - ], - ]; - } - - /** - * Illustrate how newContent cannot resolve files that have been renamed because of conflictual name. - * `decade80/under___score.jpg` should have been renamed to `decade80/under_score.jpg`, however that file already - * exists, so it got renamed to `decade80/under_score-v2.jpg` instead. However `TagsToShortcodeHelper` can not - * understand that. - */ - public function testAmbigiousCleanedName() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $underscoreFile = 3; - $trippleUnderscore = 4; - - $tagsToShortcodeHelper = new TagsToShortcodeHelper(); - - $actual = $tagsToShortcodeHelper->getNewContent( - 'link to file' - ); - $this->assertEquals( - sprintf('link to file', $underscoreFile), - $actual - ); -// In perfect world, this assertion would be true -// $this->assertEquals( -// sprintf('link to file', $trippleUnderscore), -// $actual -// ); - - $actual = $tagsToShortcodeHelper->getNewContent( - '' - ); - $this->assertEquals( - sprintf('[image src="/assets/decade80/33be1b95cb/under_score.jpg" id="%d"]', $underscoreFile), - $actual - ); -// In perfect world, this assertion would be true -// $this->assertEquals( -// sprintf('[image src="/assets/decade80/33be1b95cb/under_score-v2.jpg" id="%d"]', $trippleUnderscore), -// $actual -// ); - } -} diff --git a/tests/php/Dev/Tasks/TagsToShortcodeHelperTest.yml b/tests/php/Dev/Tasks/TagsToShortcodeHelperTest.yml deleted file mode 100644 index 3e836ebd..00000000 --- a/tests/php/Dev/Tasks/TagsToShortcodeHelperTest.yml +++ /dev/null @@ -1,48 +0,0 @@ -SilverStripe\Assets\Tests\Dev\Tasks\Shortcode\PseudoPage: - page1: - Title: 'my page' - Content: > - -

    -

    - -SilverStripe\Assets\Folder: - decadefolder: - Name: decade80 - -SilverStripe\Assets\Image: - image1: - Name: myimage.jpg - FileFilename: myimage.jpg - FileHash: 33be1b95cba0358fe54e8b13532162d52f97421c - underscoreFile: - Name: under_score.jpg - FileFilename: decade80/under_score.jpg - FileHash: 33be1b95cba0358fe54e8b13532162d52f97421c - Parent: =>SilverStripe\Assets\Folder.decadefolder - trippleUnderscoreFile: - Name: under_score.jpg - FileFilename: decade80/under_score-v2.jpg - FileHash: 33be1b95cba0358fe54e8b13532162d52f97421c - Parent: =>SilverStripe\Assets\Folder.decadefolder -SilverStripe\Assets\File: - document: - Name: document.pdf - FileFilename: document.pdf - FileHash: 0ba2141b8996a615d7484536d7a97c27a1768407 - -SilverStripe\Assets\Tests\Dev\Tasks\Shortcode\HtmlObject: - htmlObject: - HtmlLine: 'link to file' - HtmlLineNoShortCode: 'This wont be converted' - Content: 'Content Field' - -SilverStripe\Assets\Tests\Dev\Tasks\Shortcode\SubHtmlObject: - subHtmlObject: - HtmlLine: 'HtmlObject Table' - HtmlContent: 'SubHtmlObject Table' - Content: 'Content Field' - -SilverStripe\Assets\Tests\Dev\Tasks\Shortcode\NoStage: - stageless: - Content: 'Stageless Versioned Object' diff --git a/tests/php/FileTest.php b/tests/php/FileTest.php index bed6648f..ec83e65c 100644 --- a/tests/php/FileTest.php +++ b/tests/php/FileTest.php @@ -24,7 +24,6 @@ use SilverStripe\Security\Member; use SilverStripe\Security\PermissionChecker; use SilverStripe\Versioned\Versioned; -use SilverStripe\Dev\Deprecation; /** * Tests for the File class @@ -815,34 +814,6 @@ public function testJoinPaths() $this->assertEquals('', File::join_paths('/', '/')); } - /** - * Test that ini2bytes returns the number of bytes for a PHP ini style size declaration - * - * @param string $iniValue - * @param int $expected - * @dataProvider ini2BytesProvider - */ - public function testIni2Bytes($iniValue, $expected) - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $this->assertSame($expected, File::ini2bytes($iniValue)); - } - - /** - * @return array - */ - public function ini2BytesProvider() - { - return [ - ['2k', 2 * 1024], - ['512M', 512 * 1024 * 1024], - ['1024g', 1024 * 1024 * 1024 * 1024], - ['1024G', 1024 * 1024 * 1024 * 1024] - ]; - } - /** * @return AssetStore */ diff --git a/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php b/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php index 5e95c3f5..d1eaf085 100644 --- a/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php +++ b/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php @@ -8,7 +8,6 @@ use SilverStripe\Assets\FilenameParsing\FileIDHelperResolutionStrategy; use SilverStripe\Assets\FilenameParsing\FileResolutionStrategy; use SilverStripe\Assets\FilenameParsing\HashFileIDHelper; -use SilverStripe\Assets\FilenameParsing\LegacyFileIDHelper; use SilverStripe\Assets\FilenameParsing\NaturalFileIDHelper; use SilverStripe\Assets\FilenameParsing\ParsedFileID; use SilverStripe\Assets\Flysystem\LocalFilesystemAdapter; @@ -523,8 +522,8 @@ public function testFindVariant($strategy, $tuple) $this->fs->write('RootFile.txt', 'version 1'); $expectedPaths = [ - 'Folder/FolderFile__mockedvariant.pdf', 'Folder/FolderFile.pdf', + 'Folder/FolderFile__mockedvariant.pdf', 'Folder/SubFolder/SubFolderFile.pdf', ]; @@ -557,8 +556,8 @@ public function testFindHashlessVariant() $this->fs->write('Folder/FolderFile__mockedvariant.pdf', 'version 1 -- mockedvariant'); $expectedPaths = [ - ['Folder/FolderFile__mockedvariant.pdf', 'mockedvariant'], - ['Folder/FolderFile.pdf', ''] + ['Folder/FolderFile.pdf', ''], + ['Folder/FolderFile__mockedvariant.pdf', 'mockedvariant'] // The hash path won't be match, because we're not providing a hash ]; diff --git a/tests/php/FilenameParsing/FileIDHelperTester.php b/tests/php/FilenameParsing/FileIDHelperTester.php index fc733bbc..252e80f1 100644 --- a/tests/php/FilenameParsing/FileIDHelperTester.php +++ b/tests/php/FilenameParsing/FileIDHelperTester.php @@ -4,8 +4,6 @@ use SilverStripe\Assets\FilenameParsing\FileIDHelper; use SilverStripe\Assets\FilenameParsing\ParsedFileID; use SilverStripe\Dev\SapphireTest; -use SilverStripe\Assets\FilenameParsing\LegacyFileIDHelper; -use SilverStripe\Dev\Deprecation; /** * All the `FileIDHelper` have the exact same signature and very similar structure. Their basic tests will share the @@ -69,9 +67,6 @@ abstract public function variantIn(); public function testBuildFileID($expected, $input) { $help = $this->getHelper(); - if ($help instanceof LegacyFileIDHelper && Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } $this->assertEquals($expected, $help->buildFileID(...$input)); $this->assertEquals($expected, $help->buildFileID(new ParsedFileID(...$input))); } @@ -84,9 +79,6 @@ public function testBuildFileID($expected, $input) public function testDirtyBuildFildID($expected, $input) { $help = $this->getHelper(); - if ($help instanceof LegacyFileIDHelper && Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } $this->assertEquals($expected, $help->buildFileID(new ParsedFileID(...$input), null, null, false)); } @@ -97,9 +89,6 @@ public function testDirtyBuildFildID($expected, $input) public function testCleanFilename($expected, $input) { $help = $this->getHelper(); - if ($help instanceof LegacyFileIDHelper && Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } $this->assertEquals($expected, $help->cleanFilename($input)); } @@ -109,9 +98,6 @@ public function testCleanFilename($expected, $input) public function testParseFileID($input, $expected) { $help = $this->getHelper(); - if ($help instanceof LegacyFileIDHelper && Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } $parsedFiledID = $help->parseFileID($input); list($expectedFilename, $expectedHash) = $expected; @@ -131,9 +117,6 @@ public function testParseFileID($input, $expected) public function testParseBrokenFileID($input) { $help = $this->getHelper(); - if ($help instanceof LegacyFileIDHelper && Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } $parsedFiledID = $help->parseFileID($input); $this->assertNull($parsedFiledID); } @@ -145,9 +128,6 @@ public function testParseBrokenFileID($input) public function testVariantOf($variantFileID, ParsedFileID $original, $expected) { $help = $this->getHelper(); - if ($help instanceof LegacyFileIDHelper && Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } $isVariantOf = $help->isVariantOf($variantFileID, $original); $this->assertEquals($expected, $isVariantOf); } @@ -158,9 +138,6 @@ public function testVariantOf($variantFileID, ParsedFileID $original, $expected) public function testLookForVariantIn(ParsedFileID $original, $expected) { $help = $this->getHelper(); - if ($help instanceof LegacyFileIDHelper && Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } $path = $help->lookForVariantIn($original); $this->assertEquals($expected, $path); } diff --git a/tests/php/FilenameParsing/LegacyFileIDHelperTest.php b/tests/php/FilenameParsing/LegacyFileIDHelperTest.php deleted file mode 100644 index 2036d18b..00000000 --- a/tests/php/FilenameParsing/LegacyFileIDHelperTest.php +++ /dev/null @@ -1,249 +0,0 @@ -markTestSkipped('Test calls deprecated code'); - } - } - - protected function getHelper() - { - return new LegacyFileIDHelper(); - } - - public function fileIDComponents() - { - return [ - // Common use case - 'simple file' => ['sam.jpg', ['sam.jpg', '']], - 'file in folder' => ['subfolder/sam.jpg', ['subfolder/sam.jpg', '']], - 'single variant' => ['subfolder/_resampled/resizeXYZ/sam.jpg', ['subfolder/sam.jpg', '', 'resizeXYZ']], - 'multi variant' => ['subfolder/_resampled/resizeXYZ/scaleheightABC/sam.jpg', - ['subfolder/sam.jpg', '', 'resizeXYZ_scaleheightABC']], - 'root single variant' => ['_resampled/resizeXYZ/sam.jpg', ['sam.jpg', '', 'resizeXYZ']], - 'root multi variant' => ['_resampled/resizeXYZ/scaleheightABC/sam.jpg', ['sam.jpg', '', 'resizeXYZ_scaleheightABC']], - // Edge casey scenario - 'folder with underscore' => ['subfolder/under_score/_resampled/resizeXYZ/sam.jpg', [ - 'subfolder/under_score/sam.jpg', '', 'resizeXYZ' - ]], - 'filename with underscore' => ['subfolder/under_score/_resampled/resizeXYZ/sam_single-underscore.jpg', [ - 'subfolder/under_score/sam_single-underscore.jpg', '', 'resizeXYZ' - ]], - 'filename with multi underscore' => ['subfolder/under_score/sam_double_dots.tar.gz', [ - 'subfolder/under_score/sam_double_dots.tar.gz', '' - ]], - 'double dot file name' => ['subfolder/under_score/_resampled/resizeXYZ/sam_double_dots.tar.gz', [ - 'subfolder/under_score/sam_double_dots.tar.gz', '', 'resizeXYZ' - ]], - 'stack variant' => ['subfolder/under_score/_resampled/stack/variant/sam_double_dots.tar.gz', [ - 'subfolder/under_score/sam_double_dots.tar.gz', '', 'stack_variant' - ]], - ]; - } - - public function dirtyFileIDComponents() - { - return [ - ['sam.jpg', [ - 'sam.jpg', 'abcdef7890' - ]], - ['subfolder/sam.jpg', [ - 'subfolder/sam.jpg', 'abcdef7890' - ]], - ['sam__double-under-score.jpg', [ - 'sam__double-under-score.jpg', '' - ]], - ['_resampled/resizeXYZ/sam__double-under-score.jpg', [ - 'sam__double-under-score.jpg', '', 'resizeXYZ' - ]], - ['subfolder/_resampled/resizeXYZ/sam__double-under-score.jpg', [ - 'subfolder/sam__double-under-score.jpg', '', 'resizeXYZ' - ]], - ['_resampled/resizeXYZ/sam__double-under-score.jpg', [ - 'sam__double-under-score.jpg', 'abcdef7890', 'resizeXYZ' - ]], - ['subfolder/_resampled/resizeXYZ/sam__double-under-score.jpg', [ - 'subfolder/sam__double-under-score.jpg', 'abcdef7890', 'resizeXYZ' - ]], - ]; - } - - public function dirtyFileIDFromDirtyTuple() - { - // Legacy FileID helper doesn't do any cleaning, so we can reuse dirtyFileIDComponents - return $this->dirtyFileIDComponents(); - } - - function dirtyFilenames() - { - return [ - ['sam.jpg', 'sam.jpg'], - ['subfolder/sam.jpg', 'subfolder/sam.jpg'], - ['sub_folder/sam.jpg', 'sub_folder/sam.jpg'], - ['sub_folder/double__underscore.jpg', 'sub_folder/double__underscore.jpg'], - ['sub_folder/single_underscore.jpg', 'sub_folder/single_underscore.jpg'], - ['Folder/With/Backslash/file.jpg', 'Folder\With\Backslash\file.jpg'], - ]; - } - - public function brokenFileID() - { - return [ - ['/sam.jpg'], - ['/no-slash-start/sam__resizeXYZ.jpg'], - ['folder//sam.jpg'], - // Can't have an image directly in a _resampled folder without a variant - ['_resampled/sam.jpg'], - ['folder/_resampled/sam.jpg'], - ['folder/_resampled/padWw-sam.jpg'], - // We need newer format to fail on legacy - ['sam__resizeXYZ.jpg'], - ['subfolder/sam__resizeXYZ.jpg'], - ['abcdef7890/sam__resizeXYZ.jpg'], - ['subfolder/abcdef7890/sam__resizeXYZ.jpg'], - ]; - } - - public function variantOf() - { - return [ - [ - '_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('sam.jpg'), - true - ], - [ - '_resampled/InvalidMethodXYZ-sam.jpg', - new ParsedFileID('sam.jpg'), - false - ], - [ - '_resampled/PadW10-sam.jpg', - new ParsedFileID('sam.jpg'), - true - ], - [ - '_resampled/PadW10-CmsThumbnailW10-sam.jpg', - new ParsedFileID('sam.jpg'), - true - ], - [ - '_resampled/ResizeXYZ-sam.jpg', - new ParsedFileID('sam.jpg'), - false - ], - [ - 'sam.jpg', - new ParsedFileID('sam.jpg'), - true - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('folder/sam.jpg'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('folder/sam.jpg'), - true - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('folder/sam.jpg', 'abcdef7890'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('folder/sam.jpg', 'abcdef7890'), - true - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('folder/sam.jpg', '', 'ResizeXXX'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('folder/sam.jpg', '', 'ResizeXXX'), - true - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('folder/sam.jpg', 'abcdef7890', 'ResizeXXX'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('folder/sam.jpg', 'abcdef7890', 'ResizeXXX'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('wrong-folder/sam.jpg', 'abcdef7890'), - false - ], - [ - 'folder/sam.jpg', - new ParsedFileID('wrong-file-name.jpg', 'folder'), - false - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('wrong-folder/sam.jpg', 'abcdef7890'), - false - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('wrong-file-name.jpg', 'folder'), - false - ], - ]; - } - - public function variantIn() - { - return [ - [new ParsedFileID('sam.jpg', 'abcdef7890'), '_resampled'], - [new ParsedFileID('folder/sam.jpg', 'abcdef7890'), 'folder/_resampled'], - [new ParsedFileID('sam.jpg', 'abcdef7890'), '_resampled'], - [new ParsedFileID('folder/sam.jpg', 'abcdef7890'), 'folder/_resampled'], - [new ParsedFileID('folder/truncate-hash.jpg', 'abcdef78901'), 'folder/_resampled'], - [new ParsedFileID('folder/truncate-hash.jpg', 'abcdef7890', 'ResizeXXX'), 'folder/_resampled'], - ]; - } - - /** - * SS 3.0 to SS3.2 was using a different format for variant - * @return array - */ - public function ss30FileIDs() - { - return [ - 'single SS30 variant' => ['subfolder/_resampled/FitW10-sam.jpg', ['subfolder/sam.jpg', '', 'FitW10']], - 'multi SS30 variant' => ['subfolder/_resampled/FitWzEwMjQsMTAwXQ-PadW10-sam.jpg', - ['subfolder/sam.jpg', '', 'FitWzEwMjQsMTAwXQ_PadW10']], - 'SS30 variant filename starting with variant method' => - ['subfolder/_resampled/FitW10-padding-sam.jpg', ['subfolder/padding-sam.jpg', '', 'FitW10']], - 'SS30 variant filename starting with an ambiguous variant method' => - ['_resampled/PaddedImageW10-sam.jpg', ['sam.jpg', '', 'PaddedImageW10']], - ]; - } - - /** - * @dataProvider fileIDComponents - * @dataProvider ss30FileIDs - */ - public function testParseFileID($input, $expected) - { - parent::testParseFileID($input, $expected); - } -} diff --git a/tests/php/FilenameParsing/MigrationLegacyFileIDHelperTest.php b/tests/php/FilenameParsing/MigrationLegacyFileIDHelperTest.php deleted file mode 100644 index 1472bce2..00000000 --- a/tests/php/FilenameParsing/MigrationLegacyFileIDHelperTest.php +++ /dev/null @@ -1,191 +0,0 @@ -markTestSkipped('Test calls deprecated code'); - } - } - - protected function getHelper() - { - return new LegacyFileIDHelper(false); - } - - public function fileIDComponents() - { - return [ - // Common use case - ['sam.jpg', ['sam.jpg', '']], - ['subfolder/sam.jpg', ['subfolder/sam.jpg', '']], - ['subfolder/_resampled/resizeXYZ/sam.jpg', ['subfolder/sam.jpg', '', 'resizeXYZ']], - ['_resampled/resizeXYZ/sam.jpg', ['sam.jpg', '', 'resizeXYZ']], - // Edge casey scenario - ['subfolder/under_score/_resampled/resizeXYZ/sam.jpg', [ - 'subfolder/under_score/sam.jpg', '', 'resizeXYZ' - ]], - ['subfolder/under_score/_resampled/resizeXYZ/sam_single-underscore.jpg', [ - 'subfolder/under_score/sam_single-underscore.jpg', '', 'resizeXYZ' - ]], - ['subfolder/under_score/sam_double_dots.tar.gz', [ - 'subfolder/under_score/sam_double_dots.tar.gz', '' - ]], - ['subfolder/under_score/_resampled/resizeXYZ/sam_double_dots.tar.gz', [ - 'subfolder/under_score/sam_double_dots.tar.gz', '', 'resizeXYZ' - ]], - ['subfolder/under_score/_resampled/stack/variant/sam_double_dots.tar.gz', [ - 'subfolder/under_score/sam_double_dots.tar.gz', '', 'stack_variant' - ]], - ['sam__double-under-score.jpg', [ - 'sam__double-under-score.jpg', '' - ]], - ['_resampled/resizeXYZ/sam__double-under-score.jpg', [ - 'sam__double-under-score.jpg', '', 'resizeXYZ' - ]], - ['subfolder/_resampled/resizeXYZ/sam__double-under-score.jpg', [ - 'subfolder/sam__double-under-score.jpg', '', 'resizeXYZ' - ]], - ]; - } - - public function dirtyFileIDComponents() - { - return [ - ['sam.jpg', [ - 'sam.jpg', 'abcdef7890' - ]], - ['subfolder/sam.jpg', [ - 'subfolder/sam.jpg', 'abcdef7890' - ]], - ['_resampled/resizeXYZ/sam__double-under-score.jpg', [ - 'sam__double-under-score.jpg', 'abcdef7890', 'resizeXYZ' - ]], - ['subfolder/_resampled/resizeXYZ/sam__double-under-score.jpg', [ - 'subfolder/sam__double-under-score.jpg', 'abcdef7890', 'resizeXYZ' - ]], - ]; - } - - public function dirtyFileIDFromDirtyTuple() - { - // Legacy FileID helper doesn't do any cleaning, so we can reuse dirtyFileIDComponents - return $this->dirtyFileIDComponents(); - } - - function dirtyFilenames() - { - return [ - ['sam.jpg', 'sam.jpg'], - ['subfolder/sam.jpg', 'subfolder/sam.jpg'], - ['sub_folder/sam.jpg', 'sub_folder/sam.jpg'], - ['sub_folder/double__underscore.jpg', 'sub_folder/double__underscore.jpg'], - ['sub_folder/single_underscore.jpg', 'sub_folder/single_underscore.jpg'], - ['Folder/With/Backslash/file.jpg', 'Folder\With\Backslash\file.jpg'], - ]; - } - - public function brokenFileID() - { - return [ - ['/sam.jpg'], - ['/no-slash-start/sam__resizeXYZ.jpg'], - ['folder//sam.jpg'], - ]; - } - - public function variantOf() - { - return [ - [ - '_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('sam.jpg'), - true - ], - [ - 'sam.jpg', - new ParsedFileID('sam.jpg'), - true - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('folder/sam.jpg'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('folder/sam.jpg'), - true - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('folder/sam.jpg', 'abcdef7890'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('folder/sam.jpg', 'abcdef7890'), - true - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('folder/sam.jpg', '', 'ResizeXXX'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('folder/sam.jpg', '', 'ResizeXXX'), - true - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('folder/sam.jpg', 'abcdef7890', 'ResizeXXX'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('folder/sam.jpg', 'abcdef7890', 'ResizeXXX'), - true - ], - [ - 'folder/sam.jpg', - new ParsedFileID('wrong-folder/sam.jpg', 'abcdef7890'), - false - ], - [ - 'folder/sam.jpg', - new ParsedFileID('wrong-file-name.jpg', 'folder'), - false - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('wrong-folder/sam.jpg', 'abcdef7890'), - false - ], - [ - 'folder/_resampled/ResizeXYZ/sam.jpg', - new ParsedFileID('wrong-file-name.jpg', 'folder'), - false - ], - ]; - } - - public function variantIn() - { - return [ - [new ParsedFileID('sam.jpg', 'abcdef7890'), '_resampled'], - [new ParsedFileID('folder/sam.jpg', 'abcdef7890'), 'folder/_resampled'], - [new ParsedFileID('sam.jpg', 'abcdef7890'), '_resampled'], - [new ParsedFileID('folder/sam.jpg', 'abcdef7890'), 'folder/_resampled'], - [new ParsedFileID('folder/truncate-hash.jpg', 'abcdef78901'), 'folder/_resampled'], - [new ParsedFileID('folder/truncate-hash.jpg', 'abcdef7890', 'ResizeXXX'), 'folder/_resampled'], - ]; - } -} diff --git a/tests/php/RedirectFileControllerTest.php b/tests/php/RedirectFileControllerTest.php index 885e507a..037a72b1 100644 --- a/tests/php/RedirectFileControllerTest.php +++ b/tests/php/RedirectFileControllerTest.php @@ -404,25 +404,7 @@ public function testVariantRedirect($folderFixture, $filename, $ext) $response, 'Hash path variant of public file should redirect to natural path.' ); - - $response = $this->get("/assets/{$foldername}_resampled/$suffix/$filename.$ext"); - $this->assertResponse( - 301, - '', - $icoUrl, - $response, - 'SS3 Legacy path to variant should redirect.' - ); - - $response = $this->get("/assets/{$foldername}_resampled/$suffix-$filename.$ext"); - $this->assertResponse( - 301, - '', - $icoUrl, - $response, - 'SS3.0 Legacy path to variant should redirect.' - ); - + $file->setFromLocalFile(__DIR__ . '/ImageTest/test-image-high-quality.jpg', $file->FileFilename); $file->write(); $file->publishSingle(); diff --git a/tests/php/Storage/AssetStoreTest.php b/tests/php/Storage/AssetStoreTest.php index bbba7776..bcd8112b 100644 --- a/tests/php/Storage/AssetStoreTest.php +++ b/tests/php/Storage/AssetStoreTest.php @@ -9,7 +9,6 @@ use SilverStripe\Assets\File; use SilverStripe\Assets\FilenameParsing\FileIDHelper; use SilverStripe\Assets\FilenameParsing\HashFileIDHelper; -use SilverStripe\Assets\FilenameParsing\LegacyFileIDHelper; use SilverStripe\Assets\FilenameParsing\NaturalFileIDHelper; use SilverStripe\Assets\FilenameParsing\ParsedFileID; use SilverStripe\Assets\Flysystem\FlysystemAssetStore; @@ -17,7 +16,6 @@ use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\SapphireTest; -use SilverStripe\Dev\Deprecation; class AssetStoreTest extends SapphireTest { @@ -233,49 +231,6 @@ public function testConflictResolution() ); } - /** - * Test that flysystem can regenerate the original filename from fileID - */ - public function testGetOriginalFilename() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $store = new TestAssetStore(); - $this->assertEquals( - 'directory/lovely-fish.jpg', - $store->getOriginalFilename('directory/a870de278b/lovely-fish.jpg') - ); - $this->assertEquals( - 'directory/lovely-fish.jpg', - $store->getOriginalFilename('directory/a870de278b/lovely-fish__variant.jpg') - ); - $this->assertEquals( - 'directory/lovely_fish.jpg', - $store->getOriginalFilename('directory/a870de278b/lovely_fish__vari_ant.jpg') - ); - $this->assertEquals( - 'directory/lovely_fish.jpg', - $store->getOriginalFilename('directory/a870de278b/lovely_fish.jpg') - ); - $this->assertEquals( - 'lovely-fish.jpg', - $store->getOriginalFilename('a870de278b/lovely-fish.jpg') - ); - $this->assertEquals( - 'lovely-fish.jpg', - $store->getOriginalFilename('a870de278b/lovely-fish__variant.jpg') - ); - $this->assertEquals( - 'lovely_fish.jpg', - $store->getOriginalFilename('a870de278b/lovely_fish__vari__ant.jpg') - ); - $this->assertEquals( - 'lovely_fish.jpg', - $store->getOriginalFilename('a870de278b/lovely_fish.jpg') - ); - } - /** * Data provider for reversible file ids * @@ -360,17 +315,6 @@ public function dataProviderDirtyFileIDs() ]; } - /** - * Data provider for non-file IDs - */ - public function dataProviderHashlessFileIDs() - { - return [ - [ 'some/folder/file.jpg', ['Filename' => 'some/folder/file.jpg', 'Hash' => '', 'Variant' => '' ] ], - [ 'file.jpg', ['Filename' => 'file.jpg', 'Hash' => '', 'Variant' => '' ] ] - ]; - } - /** * Test internal file Id generation * @@ -389,30 +333,6 @@ public function testGetFileID($fileID, $tuple) ); } - /** - * Test reversing of FileIDs - * - * @dataProvider dataProviderFileIDs - * @dataProvider dataProviderHashlessFileIDs - * @param string $fileID File ID to parse - * @param array|null $tuple expected parsed tuple, or null if invalid - */ - public function testParseFileID($fileID, $tuple) - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $store = new TestAssetStore(); - $result = $store->parseFileID($fileID); - if (is_null($tuple)) { - $this->assertNull($result, "$fileID should be treated as invalid fileID"); - } else { - $this->assertEquals($tuple['Filename'], $result['Filename']); - $this->assertEquals($tuple['Variant'], $result['Variant']); - $this->assertEquals($tuple['Hash'], $result['Hash']); - } - } - public function testGetMetadata() { $backend = $this->getBackend(); @@ -442,122 +362,6 @@ public function testGetMetadata() $this->assertNotEmpty($puppiesMeta['timestamp']); } - /** - * Test that legacy filenames work as expected. This test is somewhate reduntant now because legacy filename - * should be ignored. - */ - public function testLegacyFilenames() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - Config::modify()->set(FlysystemAssetStore::class, 'legacy_filenames', true); - - $backend = $this->getBackend(); - - // Put a file in - $fish1 = realpath(__DIR__ . '/../ImageTest/test-image-high-quality.jpg'); - $this->assertFileExists($fish1); - $fish1Tuple = $backend->setFromLocalFile($fish1, 'directory/lovely-fish.jpg'); - $this->assertEquals( - [ - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'directory/lovely-fish.jpg', - 'Variant' => '', - ], - $fish1Tuple - ); - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/.protected/directory/a870de278b/lovely-fish.jpg'); - $this->assertEquals( - '/assets/directory/a870de278b/lovely-fish.jpg', - $backend->getAsURL($fish1Tuple['Filename'], $fish1Tuple['Hash']) - ); - - // Write a different file with same name. - // Since we are using legacy filenames, this should generate a new filename - $fish2 = realpath(__DIR__ . '/../ImageTest/test-image-low-quality.jpg'); - try { - $backend->setFromLocalFile( - $fish2, - 'directory/lovely-fish.jpg', - null, - null, - ['conflict' => AssetStore::CONFLICT_EXCEPTION] - ); - $this->fail('Writing file with different sha to same location should throw exception'); - return; - } catch (Exception $ex) { - // Success - } - - // Re-attempt this file write with conflict_rename - $fish3Tuple = $backend->setFromLocalFile( - $fish2, - 'directory/lovely-fish.jpg', - null, - null, - ['conflict' => AssetStore::CONFLICT_RENAME] - ); - $this->assertEquals( - [ - 'Hash' => '33be1b95cba0358fe54e8b13532162d52f97421c', - 'Filename' => 'directory/lovely-fish-v2.jpg', - 'Variant' => '', - ], - $fish3Tuple - ); - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/.protected/directory/33be1b95cb/lovely-fish-v2.jpg'); - $this->assertEquals( - '/assets/directory/33be1b95cb/lovely-fish-v2.jpg', - $backend->getAsURL($fish3Tuple['Filename'], $fish3Tuple['Hash']) - ); - - // Write back original file, but with CONFLICT_EXISTING. The file should not change - $backend->publish('directory/lovely-fish-v2.jpg', '33be1b95cba0358fe54e8b13532162d52f97421c'); - $fish4Tuple = $backend->setFromLocalFile( - $fish1, - 'directory/lovely-fish-v2.jpg', - null, - null, - ['conflict' => AssetStore::CONFLICT_USE_EXISTING, 'visibility' => AssetStore::VISIBILITY_PUBLIC] - ); - $this->assertEquals( - [ - 'Hash' => '33be1b95cba0358fe54e8b13532162d52f97421c', - 'Filename' => 'directory/lovely-fish-v2.jpg', - 'Variant' => '', - ], - $fish4Tuple - ); - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/directory/lovely-fish-v2.jpg'); - $this->assertEquals( - '/assets/AssetStoreTest/directory/lovely-fish-v2.jpg', - $backend->getAsURL($fish4Tuple['Filename'], $fish4Tuple['Hash']) - ); - - // Write back original file with CONFLICT_OVERWRITE. The file sha should now be updated - $fish5Tuple = $backend->setFromLocalFile( - $fish1, - 'directory/lovely-fish-v2.jpg', - null, - null, - ['conflict' => AssetStore::CONFLICT_OVERWRITE, 'visibility' => AssetStore::VISIBILITY_PUBLIC] - ); - $this->assertEquals( - [ - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'directory/lovely-fish-v2.jpg', - 'Variant' => '', - ], - $fish5Tuple - ); - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/directory/lovely-fish-v2.jpg'); - $this->assertEquals( - '/assets/AssetStoreTest/directory/lovely-fish-v2.jpg', - $backend->getAsURL($fish5Tuple['Filename'], $fish5Tuple['Hash']) - ); - } - /** * Test default conflict resolution */ @@ -569,22 +373,6 @@ public function testDefaultConflictResolution() $this->assertEquals(AssetStore::CONFLICT_OVERWRITE, $store->getDefaultConflictResolution('somevariant')); } - /** - * Test default conflict resolution for legacy filenames - */ - public function testDefaultConflictResolutionLegacy() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $store = $this->getBackend(); - - // Enable legacy filenames -- legacy filename used to have different conflict resolution prior to 1.4.0 - Config::modify()->set(FlysystemAssetStore::class, 'legacy_filenames', true); - $this->assertEquals(AssetStore::CONFLICT_OVERWRITE, $store->getDefaultConflictResolution(null)); - $this->assertEquals(AssetStore::CONFLICT_OVERWRITE, $store->getDefaultConflictResolution('somevariant')); - } - /** * Test protect / publish mechanisms */ @@ -792,80 +580,6 @@ public function testStoreLocationWritingLogic() $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/explicitelyPublicStore__variant.txt'); } - public function testGetFilesystemFor() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $store = $this->getBackend(); - - $publicFs = $store->getPublicFilesystem(); - $protectedFs = $store->getProtectedFilesystem(); - - $hash = sha1('hello'); - $hashPath = substr($hash ?? '', 0, 10) . '/hello.txt'; - $naturalPath = 'hello.txt'; - - $file = new File(); - $file->File->Filename = $naturalPath; - $file->File->Hash = $hash; - $file->write(); - $file->publishRecursive(); - - // Protected only - $protectedFs->write($hashPath, 'hello'); - $this->assertEquals( - $protectedFs, - $store->getFilesystemFor($hashPath), - $hashPath . ' is protected and does not exist on public store' - ); - $this->assertEquals( - $protectedFs, - $store->getFilesystemFor($naturalPath), - $naturalPath . ' should be rewritten to its hash path which exists on the protected' - ); - - // Public and protected - $publicFs->write($naturalPath, 'hello'); - $store->setFromString( - 'hello', - 'hello.txt', - null, - null, - ['visibility' => AssetStore::VISIBILITY_PUBLIC] - ); - $this->assertEquals( - $protectedFs, - $store->getFilesystemFor($hashPath), - $hashPath . ' exists on protected store and even if it has a resolvable public equivalent' - ); - $this->assertEquals( - $publicFs, - $store->getFilesystemFor($naturalPath), - $naturalPath . ' exists on public store even it has a protected resovlable equivalent' - ); - - // Public only - $protectedFs->delete($hashPath); - $store->setFromString( - 'hello', - 'hello.txt', - 'abcdef7890', - null, - ['visibility' => AssetStore::VISIBILITY_PUBLIC] - ); - $this->assertEquals( - $publicFs, - $store->getFilesystemFor($hashPath), - $hashPath . ' should be rewritten to its natural path which exists on the public store' - ); - $this->assertEquals( - $publicFs, - $store->getFilesystemFor($naturalPath), - $naturalPath . ' exists on public store' - ); - } - public function listOfVariantsToWrite() { $content = "The quick brown fox jumps over the lazy dog."; @@ -1007,118 +721,7 @@ public function testNormalise($fsName, array $contents, $filename, $hash, array $this->assertFalse($fs->has($notExpectedFile), "$notExpectedFile should NOT exists"); } } - - public function listOfFilesToNormaliseWithLegacy() - { - $public = AssetStore::VISIBILITY_PUBLIC; - $protected = AssetStore::VISIBILITY_PROTECTED; - - /** @var FileIDHelper $hashHelper */ - $hashHelper = new HashFileIDHelper(); - $naturalHelper = new NaturalFileIDHelper(); - $legacyHelper = new LegacyFileIDHelper(); - - $content = "The quick brown fox jumps over the lazy dog."; - $hash = sha1($content ?? ''); - $filename = 'folder/file.txt'; - $hashPath = $hashHelper->buildFileID($filename, $hash); - $legacyPath = $legacyHelper->buildFileID($filename, $hash); - - $variant = 'uppercase'; - $vContent = strtoupper($content ?? ''); - $vNatural = $naturalHelper->buildFileID($filename, $hash, $variant); - $vHash = $hashHelper->buildFileID($filename, $hash, $variant); - $vLegacy = $legacyHelper->buildFileID($filename, $hash, $variant); - - return [ - // Main file only - [$public, [$filename => $content], $filename, $hash, [$filename], [$hashPath, dirname($hashPath ?? '')]], - [$public, [$hashPath => $content], $filename, $hash, [$filename], [$hashPath, dirname($hashPath ?? '')]], - [$protected, [$filename => $content], $filename, $hash, [$hashPath], [$filename]], - [$protected, [$hashPath => $content], $filename, $hash, [$hashPath], [$filename]], - - // Main File with variant - [ - $public, - [$filename => $content, $vNatural => $vContent], - $filename, - $hash, - [$filename, $vNatural], - [$hashPath, $vHash, dirname($hashPath ?? '')] - ], - [ - $public, - [$hashPath => $content, $vHash => $vContent], - $filename, - $hash, - [$filename, $vNatural], - [$hashPath, $vHash, dirname($hashPath ?? '')] - ], - [ - $protected, - [$filename => $content, $vNatural => $vContent], - $filename, - $hash, - [$hashPath, $vHash], - [$filename, $vNatural] - ], - [ - $protected, - [$hashPath => $content, $vHash => $vContent], - $filename, - $hash, - [$hashPath, $vHash], - [$filename, $vNatural] - ], - - // SS3 variants ... the protected store doesn't resolve SS3 paths - [ - $public, - [$legacyPath => $content, $vLegacy => $vContent], - $filename, - $hash, - [$filename, $vNatural], - [$vLegacy, dirname($vLegacy ?? ''), dirname(dirname($vLegacy ?? ''))] - ], - ]; - } - - /** - * @dataProvider listOfFilesToNormaliseWithLegacy - * @param string $fsName - * @param array $contents - * @param string $filename - * @param string $hash - * @param array $expected - * @param array $notExpected - */ - public function testNormaliseWithLegacy($fsName, array $contents, $filename, $hash, array $expected, array $notExpected = []) - { - // This is a duplicate of testNormalise that a dataprovider - // with the deprecated class LegacyFileIDHelper - // Delete the dataprovider at the same time as deleting this test - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $this->writeDummyFiles($fsName, $contents); - - $results = $this->getBackend()->normalise($filename, $hash); - - $this->assertEquals($filename, $results['Filename']); - $this->assertEquals($hash, $results['Hash']); - - $fs = $this->getFilesystem($fsName); - - foreach ($expected as $expectedFile) { - $this->assertTrue($fs->fileExists($expectedFile), "$expectedFile should exists"); - $this->assertNotEmpty($fs->read($expectedFile), "$expectedFile should be non empty"); - } - - foreach ($notExpected as $notExpectedFile) { - $this->assertFalse($fs->fileExists($notExpectedFile), "$notExpectedFile should NOT exists"); - } - } - + public function listOfFileIDsToNormalise() { $public = AssetStore::VISIBILITY_PUBLIC; @@ -1248,153 +851,6 @@ public function testNormalisePath( } } - public function listOfFileIDsToNormaliseWithLegacy() - { - $public = AssetStore::VISIBILITY_PUBLIC; - $protected = AssetStore::VISIBILITY_PROTECTED; - - /** @var FileIDHelper $hashHelper */ - $hashHelper = new HashFileIDHelper(); - $naturalHelper = new NaturalFileIDHelper(); - $legacyHelper = new LegacyFileIDHelper(); - - $content = "The quick brown fox jumps over the lazy dog."; - $hash = sha1($content ?? ''); - $filename = 'folder/file.txt'; - $hashPath = $hashHelper->buildFileID($filename, $hash); - $legacyPath = $legacyHelper->buildFileID($filename, $hash); - - $variant = 'uppercase'; - $vContent = strtoupper($content ?? ''); - $vNatural = $naturalHelper->buildFileID($filename, $hash, $variant); - $vHash = $hashHelper->buildFileID($filename, $hash, $variant); - $vLegacy = $legacyHelper->buildFileID($filename, $hash, $variant); - - return [ - // Main file only - [$public, [$filename => $content], $filename, [$filename], [$hashPath, dirname($hashPath ?? '')]], - [$public, [$hashPath => $content], $hashPath, [$filename], [$hashPath, dirname($hashPath ?? '')]], - [$protected, [$filename => $content], $filename, [$hashPath], [$filename]], - [$protected, [$hashPath => $content], $hashPath, [$hashPath], [$filename]], - - // Main File with variant - [ - $public, - [$filename => $content, $vNatural => $vContent], - $filename, - [$filename, $vNatural], - [$hashPath, $vHash, dirname($hashPath ?? '')] - ], - [ - $public, - [$hashPath => $content, $vHash => $vContent], - $hashPath, - [$filename, $vNatural], - [$hashPath, $vHash, dirname($hashPath ?? '')] - ], - [ - $protected, - [$filename => $content, $vNatural => $vContent], - $filename, - [$hashPath, $vHash], - [$filename, $vNatural] - ], - [ - $protected, - [$hashPath => $content, $vHash => $vContent], - $hashPath, - [$hashPath, $vHash], - [$filename, $vNatural] - ], - - // SS3 variants ... the protected store doesn't resolve SS3 paths - [ - $public, - [$legacyPath => $content, $vLegacy => $vContent], - $legacyPath, - [$filename, $vNatural], - [$vLegacy, dirname($vLegacy ?? ''), dirname(dirname($vLegacy ?? ''))] - ], - - // Test files with a parent folder that could be confused for an hash folder - 'natural path in public store with 10-char folder' => [ - $public, - ['multimedia/video.mp4' => $content], - 'multimedia/video.mp4', - ['multimedia/video.mp4'], - [], - 'multimedia/video.mp4' - ], - 'natural path in protected store with 10-char folder' => [ - $protected, - ['multimedia/video.mp4' => $content], - 'multimedia/video.mp4', - [$hashHelper->buildFileID('multimedia/video.mp4', $hash)], - [], - 'multimedia/video.mp4' - ], - 'natural path in public store with 10-hexadecimal-char folder' => [ - $public, - ['0123456789/video.mp4' => $content], - '0123456789/video.mp4', - ['0123456789/video.mp4'], - [], - '0123456789/video.mp4' - ], - 'natural path in protected store with 10-hexadecimal-char folder' => [ - $protected, - ['abcdef7890/video.mp4' => $content], - 'abcdef7890/video.mp4', - [$hashHelper->buildFileID('abcdef7890/video.mp4', $hash)], - [], - 'abcdef7890/video.mp4' - ], - ]; - } - - /** - * @dataProvider listOfFileIDsToNormaliseWithLegacy - * @param string $fsName - * @param array $contents - * @param string $fileID - * @param array $expected - * @param array $notExpected - */ - public function testNormalisePathWithLegacy( - $fsName, - array $contents, - $fileID, - array $expected, - array $notExpected = [], - $expectedFilename = 'folder/file.txt' - ) { - // This is a duplicate of testNormalise that a dataprovider - // with the deprecated class LegacyFileIDHelper - // Delete the dataprovider at the same time as deleting this test - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $this->writeDummyFiles($fsName, $contents); - - $results = $this->getBackend()->normalisePath($fileID); - - $this->assertEquals($expectedFilename, $results['Filename']); - $this->assertTrue( - strpos(sha1("The quick brown fox jumps over the lazy dog."), $results['Hash'] ?? '') === 0 - ); - - $fs = $this->getFilesystem($fsName); - - foreach ($expected as $expectedFile) { - $this->assertTrue($fs->fileExists($expectedFile), "$expectedFile should exists"); - $this->assertNotEmpty($fs->read($expectedFile), "$expectedFile should be non empty"); - } - - foreach ($notExpected as $notExpectedFile) { - $this->assertFalse($fs->fileExists($notExpectedFile), "$notExpectedFile should NOT exists"); - } - } - /** * @param $fs * @return Filesystem diff --git a/tests/php/Storage/AssetStoreTest/TestAssetStore.php b/tests/php/Storage/AssetStoreTest/TestAssetStore.php deleted file mode 100644 index eab5541b..00000000 --- a/tests/php/Storage/AssetStoreTest/TestAssetStore.php +++ /dev/null @@ -1,14 +0,0 @@ -merge(DefaultAssetNameGenerator::class, 'version_prefix', ''); + Config::modify()->set(DefaultAssetNameGenerator::class, 'version_prefix', ''); $generator = new DefaultAssetNameGenerator('folder/MyFile-001.jpg'); $suggestions = iterator_to_array($generator); @@ -64,7 +64,7 @@ public function testWithoutPrefix() public function testPathsNormalised() { - Config::modify()->merge(DefaultAssetNameGenerator::class, 'version_prefix', '-v'); + Config::modify()->set(DefaultAssetNameGenerator::class, 'version_prefix', '-v'); $generator = new DefaultAssetNameGenerator('/some\folder/MyFile.jpg'); $suggestions = iterator_to_array($generator); $this->assertEquals(100, count($suggestions ?? [])); @@ -80,7 +80,7 @@ public function testPathsNormalised() */ public function testWithDefaultPrefix() { - Config::modify()->merge(DefaultAssetNameGenerator::class, 'version_prefix', '-v'); + Config::modify()->set(DefaultAssetNameGenerator::class, 'version_prefix', '-v'); // Test with item that doesn't contain the prefix $generator = new DefaultAssetNameGenerator('folder/MyFile-001.jpg'); @@ -120,7 +120,7 @@ public function testWithDefaultPrefix() public function testFolderWithoutDefaultPrefix() { - Config::modify()->merge(DefaultAssetNameGenerator::class, 'version_prefix', ''); + Config::modify()->set(DefaultAssetNameGenerator::class, 'version_prefix', ''); $generator = new DefaultAssetNameGenerator('folder/subfolder'); $suggestions = iterator_to_array($generator); @@ -134,7 +134,7 @@ public function testFolderWithoutDefaultPrefix() public function testFolderWithDefaultPrefix() { - Config::modify()->merge(DefaultAssetNameGenerator::class, 'version_prefix', '-v'); + Config::modify()->set(DefaultAssetNameGenerator::class, 'version_prefix', '-v'); $generator = new DefaultAssetNameGenerator('folder/subfolder'); $suggestions = iterator_to_array($generator); diff --git a/tests/php/VersionedFilesMigratorTest.php b/tests/php/VersionedFilesMigratorTest.php deleted file mode 100644 index 613449c3..00000000 --- a/tests/php/VersionedFilesMigratorTest.php +++ /dev/null @@ -1,106 +0,0 @@ -getTestVersionDirectories() as $path) { - Filesystem::makeFolder($path); - } - } - - protected function tearDown(): void - { - TestAssetStore::reset(); - Filesystem::removeFolder($this->getBasePath()); - parent::tearDown(); - } - - /** - * Test delete migration - */ - public function testMigrationDeletes() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $migrator = VersionedFilesMigrator::create( - VersionedFilesMigrator::STRATEGY_DELETE, - TestAssetStore::base_path(), - false - ); - $migrator->migrate(); - - foreach ($this->getTestVersionDirectories() as $dir) { - $path = Path::join(BASE_PATH, $dir); - $this->assertFalse(is_dir($path ?? ''), $dir . ' still exists!'); - } - } - - /** - * Test protect migration - */ - public function testMigrationProtects() - { - if (Deprecation::isEnabled()) { - $this->markTestSkipped('Test calls deprecated code'); - } - $migrator = VersionedFilesMigrator::create( - VersionedFilesMigrator::STRATEGY_PROTECT, - TestAssetStore::base_path(), - false - ); - $migrator->migrate(); - - foreach ($this->getTestVersionDirectories() as $dir) { - $path = Path::join($dir, '.htaccess'); - $this->assertTrue(file_exists($path ?? ''), $path . ' does not exist'); - } - } - - private function getTestVersionDirectories() - { - $base = TestAssetStore::base_path(); - return [ - Path::join($base, 'folder1', '_versions'), - Path::join($base, 'folder2', '_versions'), - Path::join($base, 'folder1', 'subfolder1', '_versions') - ]; - } -} From 9c31afbe2b68040a450c99775ee7cbc9ff711e1a Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Tue, 6 Sep 2022 17:26:06 +0100 Subject: [PATCH 08/32] FIX: Ensure attributes are stored against the Image record (fixes #513) --- src/ImageManipulation.php | 17 ++++------- tests/php/ImageManipulationTest.php | 28 ++++++++++++++++--- .../LazyLoadAccessorExtension.php | 13 +++++++++ 3 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 tests/php/ImageManipulationTest/LazyLoadAccessorExtension.php diff --git a/src/ImageManipulation.php b/src/ImageManipulation.php index f656a044..5f75a9c6 100644 --- a/src/ImageManipulation.php +++ b/src/ImageManipulation.php @@ -1072,23 +1072,15 @@ protected function castDimension($value, $dimension) */ private function copyImageBackend(): AssetContainer { - // Store result in new DBFile instance - /** @var DBFile $file */ - $file = DBField::create_field( - 'DBFile', - [ - 'Filename' => $this->getFilename(), - 'Hash' => $this->getHash(), - 'Variant' => $this->getVariant() - ] - ); + $file = clone $this; $backend = $this->getImageBackend(); if ($backend) { $file->setImageBackend($backend); } - return $file->setOriginal($this); + $file->File->setOriginal($this); + return $file; } /** @@ -1107,6 +1099,9 @@ public function setAttribute($name, $value) [$name => $value] )); + // If this file has already been rendered then AttributesHTML will be cached, so we have to clear the cache + $file->objCacheClear(); + return $file; } diff --git a/tests/php/ImageManipulationTest.php b/tests/php/ImageManipulationTest.php index f17a18d5..e302bcd9 100644 --- a/tests/php/ImageManipulationTest.php +++ b/tests/php/ImageManipulationTest.php @@ -2,19 +2,16 @@ namespace SilverStripe\Assets\Tests; -use InvalidArgumentException; use Prophecy\Prophecy\ObjectProphecy; use Silverstripe\Assets\Dev\TestAssetStore; use SilverStripe\Assets\File; use SilverStripe\Assets\Folder; use SilverStripe\Assets\Image; -use SilverStripe\Assets\Image_Backend; use SilverStripe\Assets\InterventionBackend; -use SilverStripe\Assets\Storage\AssetContainer; use SilverStripe\Assets\Storage\AssetStore; use SilverStripe\Assets\Storage\DBFile; +use SilverStripe\Assets\Tests\ImageManipulationTest\LazyLoadAccessorExtension; use SilverStripe\Core\Config\Config; -use SilverStripe\Core\Convert; use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\SapphireTest; use SilverStripe\View\SSViewer; @@ -363,6 +360,29 @@ public function testBadLazyLoad($val) ); } + public function testLazyLoadIsAccessibleInExtensions() + { + Image::add_extension(LazyLoadAccessorExtension::class); + + /** @var Image $origin */ + $image = $this->objFromFixture(Image::class, 'imageWithTitle'); + + $this->assertTrue( + $image->LazyLoad(true)->getLazyLoadValueViaExtension(), + 'Incorrect LazyLoad value reported by extension' + ); + $this->assertFalse( + $image->LazyLoad(false)->getLazyLoadValueViaExtension(), + 'Incorrect LazyLoad value reported by extension' + ); + $this->assertTrue( + $image->LazyLoad(false)->LazyLoad(true)->getLazyLoadValueViaExtension(), + 'Incorrect LazyLoad value reported by extension' + ); + + Image::remove_extension(LazyLoadAccessorExtension::class); + } + public function renderProvider() { $alt = 'This is a image Title'; diff --git a/tests/php/ImageManipulationTest/LazyLoadAccessorExtension.php b/tests/php/ImageManipulationTest/LazyLoadAccessorExtension.php new file mode 100644 index 00000000..f210e43f --- /dev/null +++ b/tests/php/ImageManipulationTest/LazyLoadAccessorExtension.php @@ -0,0 +1,13 @@ +getOwner()->IsLazyLoaded(); + } +} From 459d421ddcb253bf86ce0904c52692781fb6cf7c Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Fri, 9 Dec 2022 10:03:32 +1300 Subject: [PATCH 09/32] FIX Cast absoluteUrl() argument to string --- src/File.php | 2 +- src/Storage/DBFile.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/File.php b/src/File.php index 1652438b..9a27c9af 100644 --- a/src/File.php +++ b/src/File.php @@ -869,7 +869,7 @@ public function getAbsoluteURL() { $url = $this->getURL(); if ($url) { - return Director::absoluteURL($url); + return Director::absoluteURL((string) $url); } return null; } diff --git a/src/Storage/DBFile.php b/src/Storage/DBFile.php index 70a428fc..d75e61ec 100644 --- a/src/Storage/DBFile.php +++ b/src/Storage/DBFile.php @@ -326,7 +326,7 @@ public function getAbsoluteURL() if (!$this->exists()) { return null; } - return Director::absoluteURL($this->getURL()); + return Director::absoluteURL((string) $this->getURL()); } public function getMetaData() From dfc391912e3120a618e7213c5c282c961dd2bfb4 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Tue, 13 Dec 2022 12:11:40 +1300 Subject: [PATCH 10/32] ENH PHP 8.2 support --- src/Flysystem/Filesystem.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Flysystem/Filesystem.php b/src/Flysystem/Filesystem.php index 3fa0e55f..7c4850b2 100644 --- a/src/Flysystem/Filesystem.php +++ b/src/Flysystem/Filesystem.php @@ -10,6 +10,7 @@ class Filesystem extends LeagueFilesystem { private $adapter; + private PathNormalizer $pathNormalizer; public function __construct( FilesystemAdapter $adapter, From 20ed19cdc3c39acae2b4d242ea3a6d0d480399df Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Thu, 15 Dec 2022 16:40:23 +1300 Subject: [PATCH 11/32] MNT Make asset variant unit tests order agnostic --- .../FileIDHelperResolutionStrategyTest.php | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php b/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php index d1eaf085..154a728d 100644 --- a/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php +++ b/tests/php/FilenameParsing/FileIDHelperResolutionStrategyTest.php @@ -530,14 +530,13 @@ public function testFindVariant($strategy, $tuple) $variantGenerator = $strategy->findVariants($tuple, $this->fs); /** @var ParsedFileID $parsedFileID */ + $c = 0; foreach ($variantGenerator as $parsedFileID) { - $this->assertNotEmpty($expectedPaths); - $expectedPath = array_shift($expectedPaths); - $this->assertEquals($expectedPath, $parsedFileID->getFileID()); + $this->assertTrue(in_array($parsedFileID->getFileID(), $expectedPaths)); $this->assertEquals('mockedvariant', $parsedFileID->getVariant()); + $c++; } - - $this->assertEmpty($expectedPaths); + $this->assertSame(count($expectedPaths), $c); } public function testFindHashlessVariant() @@ -555,24 +554,23 @@ public function testFindHashlessVariant() ); $this->fs->write('Folder/FolderFile__mockedvariant.pdf', 'version 1 -- mockedvariant'); - $expectedPaths = [ - ['Folder/FolderFile.pdf', ''], - ['Folder/FolderFile__mockedvariant.pdf', 'mockedvariant'] + $expectedPathsVariants = [ + 'Folder/FolderFile.pdf|', + 'Folder/FolderFile__mockedvariant.pdf|mockedvariant' // The hash path won't be match, because we're not providing a hash ]; $variantGenerator = $strategy->findVariants(new ParsedFileID('Folder/FolderFile.pdf'), $this->fs); + $c = 0; /** @var ParsedFileID $parsedFileID */ foreach ($variantGenerator as $parsedFileID) { - $this->assertNotEmpty($expectedPaths, 'More files were returned than expected'); - $expectedPath = array_shift($expectedPaths); - $this->assertEquals($expectedPath[0], $parsedFileID->getFileID()); - $this->assertEquals($expectedPath[1], $parsedFileID->getVariant()); + $pathVariant = implode('|', [$parsedFileID->getFileID(), $parsedFileID->getVariant()]); + $this->assertTrue(in_array($pathVariant, $expectedPathsVariants)); $this->assertEquals($expectedHash, $parsedFileID->getHash()); + $c++; } - - $this->assertEmpty($expectedPaths, "Not all expected files were returned"); + $this->assertSame(count($expectedPathsVariants), $c); } public function testParseFileID() From 0784372755c8201284faaa4b08cd976ba6e3abb3 Mon Sep 17 00:00:00 2001 From: Maxime Rainville Date: Wed, 11 Jan 2023 11:12:00 +1300 Subject: [PATCH 12/32] API Removed ReturnTypeWillChange annotation --- src/Storage/DefaultAssetNameGenerator.php | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Storage/DefaultAssetNameGenerator.php b/src/Storage/DefaultAssetNameGenerator.php index 1c9b830f..319834c6 100644 --- a/src/Storage/DefaultAssetNameGenerator.php +++ b/src/Storage/DefaultAssetNameGenerator.php @@ -120,8 +120,7 @@ protected function getPrefix() return Config::inst()->get(__CLASS__, 'version_prefix'); } - #[\ReturnTypeWillChange] - public function current() + public function current(): mixed { $version = $this->version; @@ -146,26 +145,22 @@ public function current() return $filename; } - #[\ReturnTypeWillChange] - public function key() + public function key(): mixed { return $this->version - $this->first; } - #[\ReturnTypeWillChange] - public function next() + public function next(): void { $this->version++; } - #[\ReturnTypeWillChange] - public function rewind() + public function rewind(): void { $this->version = $this->first; } - #[\ReturnTypeWillChange] - public function valid() + public function valid(): bool { return $this->version < $this->max + $this->first; } From 4bf7f6f02ad2c33d8543444b4f83a857b71f72e7 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Thu, 12 Jan 2023 17:05:42 +1300 Subject: [PATCH 13/32] MNT Fix tests to work with no trailing slash on base url --- tests/php/FileTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/php/FileTest.php b/tests/php/FileTest.php index ec83e65c..b22190ab 100644 --- a/tests/php/FileTest.php +++ b/tests/php/FileTest.php @@ -390,7 +390,7 @@ public function testGetURL() $rootfile->publishSingle(); $this->logOut(); $this->logInWithPermission('SOME_PERMISSIONS'); - + $this->assertEquals('/assets/FileTest/FileTest.txt', $rootfile->getURL()); $session = Controller::curr()->getRequest()->getSession(); @@ -406,7 +406,7 @@ public function testGetSourceURL() // Links to incorrect base (assets/ rather than assets/FileTest) // because ProtectedAdapter doesn't know about custom base dirs in TestAssetStore $this->assertEquals('/assets/55b443b601/FileTest.txt', $rootfile->getSourceURL()); - + // Login as ADMIN and grant session access by default $session = Controller::curr()->getRequest()->getSession(); $granted = $session->get(FlysystemAssetStore::GRANTS_SESSION); @@ -426,7 +426,7 @@ public function testGetSourceURL() $rootfile->publishSingle(); $this->logOut(); $this->logInWithPermission('SOME_PERMISSIONS'); - + $this->assertEquals('/assets/FileTest/FileTest.txt', $rootfile->getSourceURL()); $session = Controller::curr()->getRequest()->getSession(); @@ -442,13 +442,13 @@ public function testGetAbsoluteURL() // Links to incorrect base (assets/ rather than assets/FileTest) // because ProtectedAdapter doesn't know about custom base dirs in TestAssetStore $this->assertEquals( - Director::absoluteBaseURL() . 'assets/55b443b601/FileTest.txt', + Controller::join_links(Director::absoluteBaseURL(), 'assets/55b443b601/FileTest.txt'), $rootfile->getAbsoluteURL() ); $rootfile->publishSingle(); $this->assertEquals( - Director::absoluteBaseURL() . 'assets/FileTest/FileTest.txt', + Controller::join_links(Director::absoluteBaseURL(), 'assets/FileTest/FileTest.txt'), $rootfile->getAbsoluteURL() ); } From 9a1e0941f93d876b5d6e188cb669512490ff13f4 Mon Sep 17 00:00:00 2001 From: Maxime Rainville Date: Mon, 16 Jan 2023 15:46:54 +1300 Subject: [PATCH 14/32] API Use more specific return type than those mandated by the interface Co-authored-by: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> --- src/Storage/DefaultAssetNameGenerator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Storage/DefaultAssetNameGenerator.php b/src/Storage/DefaultAssetNameGenerator.php index 319834c6..b6eac293 100644 --- a/src/Storage/DefaultAssetNameGenerator.php +++ b/src/Storage/DefaultAssetNameGenerator.php @@ -120,7 +120,7 @@ protected function getPrefix() return Config::inst()->get(__CLASS__, 'version_prefix'); } - public function current(): mixed + public function current(): string { $version = $this->version; @@ -145,7 +145,7 @@ public function current(): mixed return $filename; } - public function key(): mixed + public function key(): int { return $this->version - $this->first; } From c43e5fb936cfff743d8e870849ce1d0efb07753d Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Fri, 20 Jan 2023 17:15:31 +1300 Subject: [PATCH 15/32] MNT Remove legacy upgrader config --- .upgrade.yml | 145 ------------------ tests/php/AssetControlExtensionTest.php | 1 - tests/php/FileTest.php | 1 - .../FlysystemAssetStoreUpdateResponseTest.php | 3 - tests/php/FolderTest.php | 1 - tests/php/GDImageTest.php | 1 - tests/php/ImageManipulationTest.php | 1 - tests/php/ImageTest.php | 1 - tests/php/ImagickImageTest.php | 1 - tests/php/ProtectedFileControllerTest.php | 3 - tests/php/RedirectFileControllerTest.php | 3 - .../RedirectKeepArchiveFileControllerTest.php | 1 - .../Shortcodes/FileShortcodeProviderTest.php | 3 - .../Shortcodes/ImageShortcodeProviderTest.php | 3 - tests/php/Storage/AssetStoreTest.php | 3 - tests/php/Storage/DBFileTest.php | 3 - tests/php/UploadTest.php | 3 - 17 files changed, 177 deletions(-) delete mode 100644 .upgrade.yml diff --git a/.upgrade.yml b/.upgrade.yml deleted file mode 100644 index c4a99ed1..00000000 --- a/.upgrade.yml +++ /dev/null @@ -1,145 +0,0 @@ -fileExtensions: - - php -mappings: - SilverStripe\Filesystem\Storage\AssetContainer: SilverStripe\Assets\Storage\AssetContainer - AssetNameGenerator: SilverStripe\Assets\Storage\AssetNameGenerator - SilverStripe\Filesystem\Storage\AssetNameGenerator: SilverStripe\Assets\Storage\AssetNameGenerator - AssetStore: SilverStripe\Assets\Storage\AssetStore - SilverStripe\Filesystem\Storage\AssetStore: SilverStripe\Assets\Storage\AssetStore - SilverStripe\Filesystem\Storage\AssetStoreRouter: SilverStripe\Assets\Storage\AssetStoreRouter - SilverStripe\Filesystem\Storage\DBFile: SilverStripe\Assets\Storage\DBFile - SilverStripe\Filesystem\Storage\DefaultAssetNameGenerator: SilverStripe\Assets\Storage\DefaultAssetNameGenerator - GeneratedAssetHandler: SilverStripe\Assets\Storage\GeneratedAssetHandler - SilverStripe\Filesystem\Storage\GeneratedAssetHandler: SilverStripe\Assets\Storage\GeneratedAssetHandler - SilverStripe\Filesystem\Storage\ProtectedFileController: SilverStripe\Assets\Storage\ProtectedFileController - ProtectedFileController: SilverStripe\Assets\Storage\ProtectedFileController - SilverStripe\Filesystem\Flysystem\AssetAdapter: SilverStripe\Assets\Flysystem\AssetAdapter - SilverStripe\Filesystem\Flysystem\FlysystemAssetStore: SilverStripe\Assets\Flysystem\FlysystemAssetStore - SilverStripe\Filesystem\Storage\FlysystemGeneratedAssetHandler: SilverStripe\Assets\Flysystem\GeneratedAssets - SilverStripe\Assets\Flysystem\GeneratedAssetHandler: SilverStripe\Assets\Flysystem\GeneratedAssets - SilverStripe\Filesystem\Flysystem\ProtectedAdapter: SilverStripe\Assets\Flysystem\ProtectedAdapter - SilverStripe\Filesystem\Flysystem\ProtectedAssetAdapter: SilverStripe\Assets\Flysystem\ProtectedAssetAdapter - SilverStripe\Filesystem\Flysystem\PublicAdapter: SilverStripe\Assets\Flysystem\PublicAdapter - SilverStripe\Filesystem\Flysystem\PublicAssetAdapter: SilverStripe\Assets\Flysystem\PublicAssetAdapter - SilverStripe\Filesystem\AssetControlExtension: SilverStripe\Assets\AssetControlExtension - SilverStripe\Filesystem\AssetManipulationList: SilverStripe\Assets\AssetManipulationList - SilverStripe\Filesystem\Thumbnail: SilverStripe\Assets\Thumbnail - SilverStripe\Filesystem\ImageManipulation: SilverStripe\Assets\ImageManipulation - DBFileTest: SilverStripe\Assets\Tests\Storage\DBFileTest - DBFileTest_Object: SilverStripe\Assets\Tests\Storage\DBFileTest\TestObject - DBFileTest_Subclass: SilverStripe\Assets\Tests\Storage\DBFileTest\Subclass - DBFileTest_ImageOnly: SilverStripe\Assets\Tests\Storage\DBFileTest\ImageOnly - File: SilverStripe\Assets\File - SS_FileFinder: SilverStripe\Assets\FileFinder - SilverStripe\Assets\SS_FileFinder: SilverStripe\Assets\FileFinder - FileNameFilter: SilverStripe\Assets\FileNameFilter - Filesystem: SilverStripe\Assets\Filesystem - Folder: SilverStripe\Assets\Folder - Image: SilverStripe\Assets\Image - Image_Backend: SilverStripe\Assets\Image_Backend - Upload: SilverStripe\Assets\Upload - Upload_Validator: SilverStripe\Assets\Upload_Validator - AssetControlExtensionTest: SilverStripe\Assets\Tests\AssetControlExtensionTest - AssetControlExtensionTest_VersionedObject: SilverStripe\Assets\Tests\AssetControlExtensionTest\VersionedObject - AssetControlExtensionTest_Object: SilverStripe\Assets\Tests\AssetControlExtensionTest\TestObject - AssetControlExtensionTest_ArchivedObject: SilverStripe\Assets\Tests\AssetControlExtensionTest\ArchivedObject - AssetManipulationListTest: SilverStripe\Assets\Tests\AssetManipulationListTest - FileFinderTest: SilverStripe\Assets\Tests\FileFinderTest - FileNameFilterTest: SilverStripe\Assets\Tests\FileNameFilterTest - FileTest: SilverStripe\Assets\Tests\FileTest - FileTest_MyCustomFile: SilverStripe\Assets\Tests\FileTest\MyCustomFile - FolderTest: SilverStripe\Assets\Tests\FolderTest - GDTest: SilverStripe\Assets\Tests\GDImageTest - ProtectedFileControllerTest: SilverStripe\Assets\Tests\ProtectedFileControllerTest - UploadTest: SilverStripe\Assets\Tests\UploadTest - UploadTest_Validator: SilverStripe\Assets\Tests\UploadTest\Validator - GDImageTest: SilverStripe\Assets\Tests\GDImageTest - ImageTest: SilverStripe\Assets\Tests\ImageTest - ImagickImageTest: SilverStripe\Assets\Tests\ImagickImageTest - AssetStoreTest_SpyStore: SilverStripe\Assets\Dev\TestAssetStore - SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore: SilverStripe\Assets\Dev\TestAssetStore - AssetAdapterTest: SilverStripe\Assets\Tests\Flysystem\AssetAdapterTest - FlysystemPublicAdapter: SilverStripe\Assets\Flysystem\PublicAdapter - FlysystemProtectedAdapter: SilverStripe\Assets\Flysystem\ProtectedAdapter - # Service definitions - Not classes - FlysystemPublicBackend: League\Flysystem\Filesystem.public - FlysystemProtectedBackend: League\Flysystem\Filesystem.protected -skipConfigs: - - db - - casting - - table_name - - fixed_fields - - menu_title - - allowed_actions - - plural_name - - singular_name - - owns - - searchable_fields - - summary_fields - - default_search_filter_class - - segment -skipYML: - - Filesystem -warnings: - methods: - 'SilverStripe\Assets\File->setFilename()': - message: 'Filenames should not be set directly anymore' - url: 'https://docs.silverstripe.org/en/4/changelogs/4.0.0#write-file-dataobject' - 'SilverStripe\Assets\File::handle_shortcode()': - message: 'Has been removed' - url: 'https://docs.silverstripe.org/en/4/changelogs/4.0.0#file-shortcode' - 'SilverStripe\Assets\Filesystem::sync()': - message: 'Removed' - 'SilverStripe\Assets\Folder::syncChildren()': - message: 'Removed' - 'SilverStripe\Assets\Folder::constructChild()': - message: 'Removed' - 'SilverStripe\Assets\Folder::addUploadToFolder()': - message: 'Removed' - 'SilverStripe\Assets\File::deletedatabaseOnly()': - message: 'Removed' - 'SilverStripe\Assets\File::link_shortcode_handler()': - message: 'Removed' - 'SilverStripe\Assets\File::getRelativePath()': - message: 'Removed' - 'SilverStripe\Assets\File::getFullPath()': - message: 'Removed' - 'SilverStripe\Assets\File::setParentID()': - message: 'Removed' - 'SilverStripe\Assets\Image::regenerateFormattedImages()': - message: 'Removed' - 'SilverStripe\Assets\Image::getGeneratedImages()': - message: 'Removed' - 'SilverStripe\Assets\Image::deleteFormattedImages()': - message: 'Removed' - 'SilverStripe\Assets\Image::handle_shortcode()': - message: 'moved to SilverStripe\Assets\Shortcodes\ImageShortcodeProvider::handle_shortcode()' - 'SilverStripe\Assets\Image::SetRatioSize()': - message: 'Renamed to Fit()' - replacement: 'Fit' - 'SilverStripe\Assets\Image::SetWidth()': - message: 'Renamed to ScaleWidth()' - replacement: 'ScaleWidth' - 'SilverStripe\Assets\Image::SetHeight()': - message: 'Renamed to ScaleHeight' - replacement: 'ScaleHeight' - 'SilverStripe\Assets\Image::SetSize()': - message: 'Renamed to Pad()' - replacement: 'Pad' - 'SilverStripe\Assets\Image::PaddedImage()': - message: 'Renamed to Pad()' - replacement: 'Pad' - 'SilverStripe\Assets\Image::CroppedImage()': - message: 'Renamed to Fill()' - replacement: 'Fill' - 'SilverStripe\Assets\Image::AssetLibraryPreview()': - message: 'Renamed to PreviewThumbnail()' - replacement: 'PreviewThumbnail' - 'SilverStripe\Assets\Image::AssetLibraryThumbnail()': - message: 'Renamed to CMSThumbnail()' - replacement: 'CMSThumbnail' - 'SilverStripe\Assets\Flysystem\FlysystemAssetStore->getFileID()': - message: 'Replace with getDefaultFileIDHelper()->buildFileID()' -renameWarnings: - - File - - Image diff --git a/tests/php/AssetControlExtensionTest.php b/tests/php/AssetControlExtensionTest.php index 4044b655..82bb607f 100644 --- a/tests/php/AssetControlExtensionTest.php +++ b/tests/php/AssetControlExtensionTest.php @@ -15,7 +15,6 @@ /** * Tests {@see AssetControlExtension} - * @skipUpgrade */ class AssetControlExtensionTest extends SapphireTest { diff --git a/tests/php/FileTest.php b/tests/php/FileTest.php index b22190ab..3c0b6ba1 100644 --- a/tests/php/FileTest.php +++ b/tests/php/FileTest.php @@ -27,7 +27,6 @@ /** * Tests for the File class - * @skipUpgrade */ class FileTest extends SapphireTest { diff --git a/tests/php/Flysystem/FlysystemAssetStoreUpdateResponseTest.php b/tests/php/Flysystem/FlysystemAssetStoreUpdateResponseTest.php index 2827ff95..de9f66a0 100644 --- a/tests/php/Flysystem/FlysystemAssetStoreUpdateResponseTest.php +++ b/tests/php/Flysystem/FlysystemAssetStoreUpdateResponseTest.php @@ -23,9 +23,6 @@ class FlysystemAssetStoreUpdateResponseTest extends SapphireTest private $hash; - /** - * @skipUpgrade - */ protected function setUp(): void { parent::setUp(); diff --git a/tests/php/FolderTest.php b/tests/php/FolderTest.php index eeb78ace..abbb7f41 100644 --- a/tests/php/FolderTest.php +++ b/tests/php/FolderTest.php @@ -17,7 +17,6 @@ /** * @author Ingo Schommer (ingo at silverstripe dot com) - * @skipUpgrade */ class FolderTest extends SapphireTest { diff --git a/tests/php/GDImageTest.php b/tests/php/GDImageTest.php index 4e57f66c..8a8c4e38 100644 --- a/tests/php/GDImageTest.php +++ b/tests/php/GDImageTest.php @@ -20,7 +20,6 @@ protected function setUp(): void return; } - /** @skipUpgrade */ // this is a hack because the service locator cahces config settings meaning you can't properly override them Injector::inst()->setConfigLocator(new SilverStripeServiceConfigurationLocator()); Config::modify()->set(Injector::class, ImageManager::class, [ diff --git a/tests/php/ImageManipulationTest.php b/tests/php/ImageManipulationTest.php index 8a769e41..d11a655b 100644 --- a/tests/php/ImageManipulationTest.php +++ b/tests/php/ImageManipulationTest.php @@ -21,7 +21,6 @@ /** * ImageTest is abstract and should be overridden with manipulator-specific subtests - * @skipUpgrade */ class ImageManipulationTest extends SapphireTest { diff --git a/tests/php/ImageTest.php b/tests/php/ImageTest.php index c1ef979c..90e7faab 100644 --- a/tests/php/ImageTest.php +++ b/tests/php/ImageTest.php @@ -21,7 +21,6 @@ /** * ImageTest is abstract and should be overridden with manipulator-specific subtests - * @skipUpgrade */ abstract class ImageTest extends SapphireTest { diff --git a/tests/php/ImagickImageTest.php b/tests/php/ImagickImageTest.php index d9ca5ba3..232bda9c 100644 --- a/tests/php/ImagickImageTest.php +++ b/tests/php/ImagickImageTest.php @@ -20,7 +20,6 @@ protected function setUp(): void return; } - /** @skipUpgrade */ // this is a hack because the service locator cahces config settings meaning you can't properly override them Injector::inst()->setConfigLocator(new SilverStripeServiceConfigurationLocator()); Config::modify()->set(Injector::class, ImageManager::class, [ diff --git a/tests/php/ProtectedFileControllerTest.php b/tests/php/ProtectedFileControllerTest.php index c4f0d6ec..d8f54415 100644 --- a/tests/php/ProtectedFileControllerTest.php +++ b/tests/php/ProtectedFileControllerTest.php @@ -13,9 +13,6 @@ use SilverStripe\Dev\FunctionalTest; use SilverStripe\Versioned\Versioned; -/** - * @skipUpgrade - */ class ProtectedFileControllerTest extends FunctionalTest { protected static $fixture_file = 'FileTest.yml'; diff --git a/tests/php/RedirectFileControllerTest.php b/tests/php/RedirectFileControllerTest.php index 037a72b1..446bd645 100644 --- a/tests/php/RedirectFileControllerTest.php +++ b/tests/php/RedirectFileControllerTest.php @@ -16,9 +16,6 @@ use SilverStripe\Assets\Dev\TestAssetStore; use SilverStripe\Versioned\Versioned; -/** - * @skipUpgrade - */ class RedirectFileControllerTest extends FunctionalTest { protected static $fixture_file = 'FileTest.yml'; diff --git a/tests/php/RedirectKeepArchiveFileControllerTest.php b/tests/php/RedirectKeepArchiveFileControllerTest.php index 3321ace1..9ca3fab2 100644 --- a/tests/php/RedirectKeepArchiveFileControllerTest.php +++ b/tests/php/RedirectKeepArchiveFileControllerTest.php @@ -14,7 +14,6 @@ /** * We rerun all the same test in `RedirectFileControllerTest` but with keep_archived_assets on - * @skipUpgrade */ class RedirectKeepArchiveFileControllerTest extends RedirectFileControllerTest { diff --git a/tests/php/Shortcodes/FileShortcodeProviderTest.php b/tests/php/Shortcodes/FileShortcodeProviderTest.php index 90c79c5e..8e9505cf 100644 --- a/tests/php/Shortcodes/FileShortcodeProviderTest.php +++ b/tests/php/Shortcodes/FileShortcodeProviderTest.php @@ -16,9 +16,6 @@ use SilverStripe\Versioned\Versioned; use SilverStripe\View\Parsers\ShortcodeParser; -/** - * @skipUpgrade - */ class FileShortcodeProviderTest extends SapphireTest { protected static $fixture_file = '../FileTest.yml'; diff --git a/tests/php/Shortcodes/ImageShortcodeProviderTest.php b/tests/php/Shortcodes/ImageShortcodeProviderTest.php index 647ce8a4..5a190818 100644 --- a/tests/php/Shortcodes/ImageShortcodeProviderTest.php +++ b/tests/php/Shortcodes/ImageShortcodeProviderTest.php @@ -16,9 +16,6 @@ use SilverStripe\Security\InheritedPermissions; use SilverStripe\Security\Member; -/** - * @skipUpgrade - */ class ImageShortcodeProviderTest extends SapphireTest { diff --git a/tests/php/Storage/AssetStoreTest.php b/tests/php/Storage/AssetStoreTest.php index bcd8112b..e73b6be8 100644 --- a/tests/php/Storage/AssetStoreTest.php +++ b/tests/php/Storage/AssetStoreTest.php @@ -20,9 +20,6 @@ class AssetStoreTest extends SapphireTest { - /** - * @skipUpgrade - */ protected function setUp(): void { parent::setUp(); diff --git a/tests/php/Storage/DBFileTest.php b/tests/php/Storage/DBFileTest.php index c23137cd..7ce83a84 100644 --- a/tests/php/Storage/DBFileTest.php +++ b/tests/php/Storage/DBFileTest.php @@ -8,9 +8,6 @@ use SilverStripe\Dev\SapphireTest; use SilverStripe\ORM\ValidationException; -/** - * @skipUpgrade - */ class DBFileTest extends SapphireTest { diff --git a/tests/php/UploadTest.php b/tests/php/UploadTest.php index a9b4d6bd..74a41c24 100644 --- a/tests/php/UploadTest.php +++ b/tests/php/UploadTest.php @@ -15,9 +15,6 @@ use SilverStripe\Security\Member; use SilverStripe\Versioned\Versioned; -/** - * @skipUpgrade - */ class UploadTest extends SapphireTest { /** From 28cd27e68f2726c5a2df7637beb1f0cb20ef3418 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Fri, 24 Feb 2023 10:36:50 +1300 Subject: [PATCH 16/32] ENH Use the optimal file resolution strat by default --- _config/asset.yml | 1 - tests/php/Storage/AssetStoreTest.php | 18 +++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/_config/asset.yml b/_config/asset.yml index 0977ed05..5ff61ba2 100644 --- a/_config/asset.yml +++ b/_config/asset.yml @@ -38,7 +38,6 @@ SilverStripe\Core\Injector\Injector: DefaultFileIDHelper: '%$SilverStripe\Assets\FilenameParsing\HashFileIDHelper' ResolutionFileIDHelpers: - '%$SilverStripe\Assets\FilenameParsing\HashFileIDHelper' - - '%$SilverStripe\Assets\FilenameParsing\NaturalFileIDHelper' VersionedStage: Stage --- Name: assetscore diff --git a/tests/php/Storage/AssetStoreTest.php b/tests/php/Storage/AssetStoreTest.php index e73b6be8..fd38c2d0 100644 --- a/tests/php/Storage/AssetStoreTest.php +++ b/tests/php/Storage/AssetStoreTest.php @@ -8,6 +8,8 @@ use Silverstripe\Assets\Dev\TestAssetStore; use SilverStripe\Assets\File; use SilverStripe\Assets\FilenameParsing\FileIDHelper; +use SilverStripe\Assets\FilenameParsing\FileIDHelperResolutionStrategy; +use SilverStripe\Assets\FilenameParsing\FileResolutionStrategy; use SilverStripe\Assets\FilenameParsing\HashFileIDHelper; use SilverStripe\Assets\FilenameParsing\NaturalFileIDHelper; use SilverStripe\Assets\FilenameParsing\ParsedFileID; @@ -700,6 +702,11 @@ public function listOfFilesToNormalise() */ public function testNormalise($fsName, array $contents, $filename, $hash, array $expected, array $notExpected = []) { + /** @var FileIDHelperResolutionStrategy $protectedStrat */ + $protectedStrat = Injector::inst()->get(FileResolutionStrategy::class . '.protected'); + $originalHelpers = $protectedStrat->getResolutionFileIDHelpers(); + $protectedStrat->setResolutionFileIDHelpers(array_merge($originalHelpers, [new NaturalFileIDHelper()])); + $this->writeDummyFiles($fsName, $contents); $results = $this->getBackend()->normalise($filename, $hash); @@ -717,8 +724,10 @@ public function testNormalise($fsName, array $contents, $filename, $hash, array foreach ($notExpected as $notExpectedFile) { $this->assertFalse($fs->has($notExpectedFile), "$notExpectedFile should NOT exists"); } + + $protectedStrat->setResolutionFileIDHelpers($originalHelpers); } - + public function listOfFileIDsToNormalise() { $public = AssetStore::VISIBILITY_PUBLIC; @@ -827,6 +836,11 @@ public function testNormalisePath( array $notExpected = [], $expectedFilename = 'folder/file.txt' ) { + /** @var FileIDHelperResolutionStrategy $protectedStrat */ + $protectedStrat = Injector::inst()->get(FileResolutionStrategy::class . '.protected'); + $originalHelpers = $protectedStrat->getResolutionFileIDHelpers(); + $protectedStrat->setResolutionFileIDHelpers(array_merge($originalHelpers, [new NaturalFileIDHelper()])); + $this->writeDummyFiles($fsName, $contents); $results = $this->getBackend()->normalisePath($fileID); @@ -846,6 +860,8 @@ public function testNormalisePath( foreach ($notExpected as $notExpectedFile) { $this->assertFalse($fs->has($notExpectedFile), "$notExpectedFile should NOT exists"); } + + $protectedStrat->setResolutionFileIDHelpers($originalHelpers); } /** From 4d68ca92c6d0254cfc5ce946e78b56bec8f9d084 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> Date: Tue, 28 Feb 2023 18:57:15 +1300 Subject: [PATCH 17/32] API Explicitly deprecate legacy file resolution (#543) --- src/FilenameParsing/LegacyFileIDHelper.php | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/FilenameParsing/LegacyFileIDHelper.php b/src/FilenameParsing/LegacyFileIDHelper.php index 3edc5833..bb1aa7e8 100644 --- a/src/FilenameParsing/LegacyFileIDHelper.php +++ b/src/FilenameParsing/LegacyFileIDHelper.php @@ -132,18 +132,34 @@ public function parseFileID($fileID) return null; } + // This helper is only used if the natural and hash helpers have already failed to parse the ID + // So if we can parse the ID here it means the project has a deprecated legacy file structure. + $deprecationMessage = 'Use of legacy file resolution strategies is deprecated.' + . ' See https://docs.silverstripe.org/en/4/developer_guides/files/file_migration/.' + . ' After migrating your files, change your file resolution configuration to match the defaults:' + . ' https://github.com/silverstripe/silverstripe-installer/blob/4/app/_config/assets.yml'; + + // Can't have a resampled folder without a variant if (empty($matches['variant']) && strpos($fileID ?? '', '_resampled') !== false) { - return $this->parseSilverStripe30VariantFileID($fileID); + $parsed = $this->parseSilverStripe30VariantFileID($fileID); + if ($parsed) { + Deprecation::notice('4.13.0', $deprecationMessage, Deprecation::SCOPE_GLOBAL); + } + return $parsed; } $filename = $matches['folder'] . $matches['basename'] . $matches['extension']; - return new ParsedFileID( + $parsed = new ParsedFileID( $filename, '', isset($matches['variant']) ? str_replace('/', '_', $matches['variant']) : '', $fileID ); + if ($parsed) { + Deprecation::notice('4.13.0', $deprecationMessage, Deprecation::SCOPE_GLOBAL); + } + return $parsed; } /** From 70ac63a025be57639d290dbd7fafc8b99d7332e0 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Mon, 6 Mar 2023 18:03:33 +1300 Subject: [PATCH 18/32] ENH Update translations --- .tx/config | 5 +++-- lang/bg.yml | 2 +- lang/da.yml | 49 ++++++++++++++++++++++++++++++++++++++++++++ lang/de.yml | 4 ++++ lang/en.yml | 12 ++++++++++- lang/eo.yml | 2 ++ lang/fi.yml | 49 ++++++++++++++++++++++++++++++++++++++++++++ lang/fi_FI.yml | 49 ++++++++++++++++++++++++++++++++++++++++++++ lang/fr.yml | 52 +++++++++++++++++++++++++++++++++++++++++++++++ lang/hr.yml | 39 +++++++++++++++++++++++++++++++++++ lang/id_ID.yml | 39 +++++++++++++++++++++++++++++++++++ lang/it.yml | 5 +++++ lang/pl.yml | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ lang/pl_PL.yml | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ lang/sl.yml | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ lang/sv.yml | 48 +++++++++++++++++++++++++++++++++++++++++++ 16 files changed, 516 insertions(+), 4 deletions(-) create mode 100644 lang/da.yml create mode 100644 lang/fi.yml create mode 100644 lang/fi_FI.yml create mode 100644 lang/fr.yml create mode 100644 lang/hr.yml create mode 100644 lang/id_ID.yml create mode 100644 lang/pl.yml create mode 100644 lang/pl_PL.yml create mode 100644 lang/sl.yml create mode 100644 lang/sv.yml diff --git a/.tx/config b/.tx/config index 838aecc6..d44f7b25 100644 --- a/.tx/config +++ b/.tx/config @@ -1,8 +1,9 @@ [main] host = https://www.transifex.com -[silverstripe-assets.master] +[o:silverstripe:p:silverstripe-assets:r:master] file_filter = lang/.yml source_file = lang/en.yml source_lang = en -type = YML +type = YML + diff --git a/lang/bg.yml b/lang/bg.yml index 8305d30b..6260f74f 100644 --- a/lang/bg.yml +++ b/lang/bg.yml @@ -14,7 +14,7 @@ bg: JpgType: 'JPEG файл - подходящ за снимки' JsType: 'Javascript файл' LASTEDIT: 'Последно променен' - MODIFIED: 'Редактиран' + MODIFIED: Редактиран Mp3Type: 'MP3 аудио' MpgType: 'MPEG видео' NOFILESIZE: 'Големината на файла е 0 байта' diff --git a/lang/da.yml b/lang/da.yml new file mode 100644 index 00000000..0ad28663 --- /dev/null +++ b/lang/da.yml @@ -0,0 +1,49 @@ +da: + SilverStripe\Assets\File: + AviType: 'AVI video fil' + CREATED: 'Først uploadet' + CssType: 'CSS fil' + DRAFT: Kladde + DmgType: 'Apple disk image' + DocType: 'Word dokument' + EDIT_ALL_DESCRIPTION: 'Rediger enhver fil' + EDIT_ALL_HELP: 'Rediger enhver fil på sitet, også beskyttede' + GifType: 'GIF billede - god til illustrationer' + GzType: 'GZIP komprimeret fil' + HtmlType: 'HTML fil' + INVALIDEXTENSION_SHORT: 'Endelse ikke tilladt' + IcoType: 'Ikon billedefil' + JpgType: 'JPEG billede - god til fotos' + JsType: 'Javascript fil' + LASTEDIT: 'Senest ændret' + MODIFIED: Modificeret + Mp3Type: 'MP3 lyd fil' + MpgType: 'MPEG video fil' + NOFILESIZE: 'Filstørrelse er nul bytes.' + NOVALIDUPLOAD: 'Filen er ikke gyldig til upload' + PATH: Sti + PLURALNAME: Filer + PLURALS: + one: 'En fil' + other: '{count} Filer' + PdfType: 'Adobe Acrobat PDF fil' + PngType: 'PNG billede - godt allround format' + SINGULARNAME: Fil + TOOLARGE: 'Filen er for stor, maksimalt tilladt {size}' + TiffType: 'Tagged billede format' + URL: URL + WavType: 'WAV lyd fil' + XlsType: 'Excel regneark' + ZipType: 'ZIP komprimeret fil' + SilverStripe\Assets\Folder: + PLURALNAME: Mapper + PLURALS: + one: 'En mappe' + other: '{count} Mapper' + SINGULARNAME: Mappe + SilverStripe\Assets\Image: + PLURALNAME: Billeder + PLURALS: + one: 'Et billede' + other: '{count} billeder' + SINGULARNAME: Billede diff --git a/lang/de.yml b/lang/de.yml index 91541245..d83eb50c 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -31,6 +31,10 @@ de: WavType: WAV-Audiodatei XlsType: Excel-Tabelle ZipType: 'ZIP-komprimierte Datei' + EDIT_ALL_DESCRIPTION: 'Alle Dateien bearbeiten' + EDIT_ALL_HELP: 'Alle, auch eingeschränkte, Dateien bearbeiten' + NOVALIDUPLOAD: 'Die Datei ist kein gültiger Upload' + TiffType: 'Tagged Image File Format' SilverStripe\Assets\Folder: PLURALNAME: Ordner PLURALS: diff --git a/lang/en.yml b/lang/en.yml index 058ee054..29094d0d 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -11,6 +11,7 @@ en: GifType: 'GIF image - good for diagrams' GzType: 'GZIP compressed file' HtmlType: 'HTML file' + INVALIDEXTENSION_SHORT: 'Extension is not allowed' INVALIDEXTENSION_SHORT_EXT: 'Extension ''{extension}'' is not allowed' IcoType: 'Icon image' JpgType: 'JPEG image - good for photos' @@ -27,13 +28,14 @@ en: PLURALS: one: 'A File' other: '{count} Files' + PREVIEW: Preview PdfType: 'Adobe Acrobat PDF file' PngType: 'PNG image - good general-purpose format' SINGULARNAME: File TOOLARGE: 'Filesize is too large, maximum {size} allowed' TiffType: 'Tagged image format' URL: URL - WavType: 'WAV audio file' + WavType: 'WAV audo file' XlsType: 'Excel spreadsheet' ZipType: 'ZIP compressed file' SilverStripe\Assets\Folder: @@ -50,3 +52,11 @@ en: SINGULARNAME: Image SilverStripe\Assets\ShortCodesImageShortcodeProvider: IMAGENOTFOUND: 'Image not found' + SilverStripe\Assets\Shortcodes\FileLink: + PLURALNAME: 'File Links' + PLURALS: + one: 'A File Link' + other: '{count} File Links' + SINGULARNAME: 'File Link' + SilverStripe\Assets\Shortcodes\ImageShortcodeProvider: + IMAGENOTFOUND: 'Image not found' diff --git a/lang/eo.yml b/lang/eo.yml index 07e6562f..e4956748 100644 --- a/lang/eo.yml +++ b/lang/eo.yml @@ -33,6 +33,8 @@ eo: WavType: WAV-sondosiero XlsType: Excel-kalkultabelo ZipType: 'kompaktigita ZIP-dosiero' + EDIT_ALL_DESCRIPTION: 'Redakti ajnan dosieron' + EDIT_ALL_HELP: 'Redakti ajnan dosieron en la retejo, eĉ se limigite alirebla' SilverStripe\Assets\Folder: PLURALNAME: Dosierujoj PLURALS: diff --git a/lang/fi.yml b/lang/fi.yml new file mode 100644 index 00000000..898f5f03 --- /dev/null +++ b/lang/fi.yml @@ -0,0 +1,49 @@ +fi: + SilverStripe\Assets\File: + AviType: AVI-videotiedosto + CREATED: 'Ensimmäisenä siirretty' + CssType: CSS-tiedosto + DRAFT: Vedos + DmgType: 'Apple DMG-tiedosto' + DocType: Word-dokumentti + EDIT_ALL_DESCRIPTION: 'Kaikkien tiedostojen muokkaus' + EDIT_ALL_HELP: 'Muokkaa mitä tahansa sivuston tiedostoa, myös rajoitettuja' + GifType: 'GIF-kuva, esim. graafisille kuvaajille' + GzType: 'GZIP-pakattu tiedosto' + HtmlType: HTML-tiedosto + INVALIDEXTENSION_SHORT: 'Ei sallittu tiedostopääte' + IcoType: Icon-kuva + JpgType: 'JPEG-kuva, esim. valokuville' + JsType: Javaskripti-tiedosto + LASTEDIT: 'Viimeksi muokattu' + MODIFIED: Muokattu + Mp3Type: MP3-äänitiedosto + MpgType: MPEG-videotiedosto + NOFILESIZE: 'Tiedostokoko on nolla tavua.' + NOVALIDUPLOAD: 'Tiedosto ei ole kelvollinen siirrettäväksi' + PATH: Polku + PLURALNAME: Tiedostot + PLURALS: + one: Tiedosto + other: '{count} tiedostoa' + PdfType: 'Adobe Acrobat PDF-tiedosto' + PngType: 'PNG-kuva, esim. taustattomille kuville' + SINGULARNAME: Tiedosto + TOOLARGE: 'Tiedostokoko on liian iso: suurin sallittu koko on {size}' + TiffType: TIFF-kuva + URL: URL-osoite + WavType: WAV-äänitiedosto + XlsType: Excel-laskentataulukko + ZipType: 'ZIP-pakattu tiedosto' + SilverStripe\Assets\Folder: + PLURALNAME: Kansiot + PLURALS: + one: Kansio + other: '{count} kansiota' + SINGULARNAME: Kansio + SilverStripe\Assets\Image: + PLURALNAME: Kuvat + PLURALS: + one: Kuva + other: '{count} kuvaa' + SINGULARNAME: Kuva diff --git a/lang/fi_FI.yml b/lang/fi_FI.yml new file mode 100644 index 00000000..c1d891fa --- /dev/null +++ b/lang/fi_FI.yml @@ -0,0 +1,49 @@ +fi_FI: + SilverStripe\Assets\File: + AviType: AVI-videotiedosto + CREATED: 'Ensimmäisenä siirretty' + CssType: CSS-tiedosto + DRAFT: Vedos + DmgType: 'Apple DMG-tiedosto' + DocType: Word-dokumentti + EDIT_ALL_DESCRIPTION: 'Kaikkien tiedostojen muokkaus' + EDIT_ALL_HELP: 'Muokkaa mitä tahansa sivuston tiedostoa, myös rajoitettuja' + GifType: 'GIF-kuva, esim. graafisille kuvaajille' + GzType: 'GZIP-pakattu tiedosto' + HtmlType: HTML-tiedosto + INVALIDEXTENSION_SHORT: 'Ei sallittu tiedostopääte' + IcoType: Icon-kuva + JpgType: 'JPEG-kuva, esim. valokuville' + JsType: Javaskripti-tiedosto + LASTEDIT: 'Viimeksi muokattu' + MODIFIED: Muokattu + Mp3Type: MP3-äänitiedosto + MpgType: MPEG-videotiedosto + NOFILESIZE: 'Tiedostokoko on nolla tavua.' + NOVALIDUPLOAD: 'Tiedosto ei ole kelvollinen siirrettäväksi' + PATH: Polku + PLURALNAME: Tiedostot + PLURALS: + one: Tiedosto + other: '{count} tiedostoa' + PdfType: 'Adobe Acrobat PDF-tiedosto' + PngType: 'PNG-kuva, esim. taustattomille kuville' + SINGULARNAME: Tiedosto + TOOLARGE: 'Tiedostokoko on liian iso: suurin sallittu koko on {size}' + TiffType: TIFF-kuva + URL: URL-osoite + WavType: WAV-äänitiedosto + XlsType: Excel-laskentataulukko + ZipType: 'ZIP-pakattu tiedosto' + SilverStripe\Assets\Folder: + PLURALNAME: Kansiot + PLURALS: + one: Kansio + other: '{count} kansiota' + SINGULARNAME: Kansio + SilverStripe\Assets\Image: + PLURALNAME: Kuvat + PLURALS: + one: Kuva + other: '{count} kuvaa' + SINGULARNAME: Kuva diff --git a/lang/fr.yml b/lang/fr.yml new file mode 100644 index 00000000..1a11acc8 --- /dev/null +++ b/lang/fr.yml @@ -0,0 +1,52 @@ +fr: + SilverStripe\Assets\File: + AviType: 'Fichier vidéo AVI' + CREATED: 'Premier téléchargement' + CssType: 'Fichier CSS' + DRAFT: Brouillon + DmgType: 'Image disque Apple' + DocType: 'Document Word' + EDIT_ALL_DESCRIPTION: 'Editer tous les fichiers' + EDIT_ALL_HELP: 'Editer tout fichier du site, même si des restrictions s''appliquent' + GifType: 'Image GIF - idéal pour diagrammes' + GzType: 'Fichier compressé GZIP' + HtmlType: 'Fichier HTML' + INVALIDEXTENSION_SHORT: 'Extension de fichier non autorisée' + IcoType: Icône + JpgType: 'Image JPEG - idéal pour photos' + JsType: 'Fichier javascript' + LASTEDIT: 'Dernière modification' + MODIFIED: Modifié + Mp3Type: 'Fichier audio MP3' + MpgType: 'Fichier vidéo MPEG' + NOFILESIZE: 'Taille du fichier : 0 octet' + NOVALIDUPLOAD: 'Fichier non valide pour un téléchargement' + PATH: Chemin + PLURALNAME: Fichiers + PLURALS: + one: '1 fichier' + many: '{count} fichiers' + other: '{count} fichiers' + PdfType: 'Fichier PDF Adobe Acrobat' + PngType: 'Image PNG - format passe-partout' + SINGULARNAME: Fichier + TOOLARGE: 'Fichier trop volumineux, {size} maximum autorisés' + TiffType: 'Image TIFF' + URL: URL + WavType: 'Fichier audio WAV' + XlsType: 'Tableur Excel' + ZipType: 'Fichier compressé ZIP' + SilverStripe\Assets\Folder: + PLURALNAME: Dossiers + PLURALS: + one: '1 dossier' + many: '{count} dossiers' + other: '{count} dossiers' + SINGULARNAME: Dossier + SilverStripe\Assets\Image: + PLURALNAME: Images + PLURALS: + one: '1 image' + many: '{count} images' + other: '{count} images' + SINGULARNAME: Image diff --git a/lang/hr.yml b/lang/hr.yml new file mode 100644 index 00000000..7bde362c --- /dev/null +++ b/lang/hr.yml @@ -0,0 +1,39 @@ +hr: + SilverStripe\Assets\File: + AviType: 'AVI video datoteka' + CREATED: 'Prvi prijenos' + CssType: 'CSS datoteka' + DRAFT: Nacrt + DmgType: 'Apple disk slika' + DocType: 'Word dokument' + EDIT_ALL_DESCRIPTION: 'Uredi bilo koju datoteku' + EDIT_ALL_HELP: 'Uredi bilo koju datoteku na siteu, čak i zaštićene' + GifType: 'GIF slika - dobra za diagrame' + GzType: 'GZIP komprimirana datoteka' + HtmlType: 'HTML datoteka' + INVALIDEXTENSION_SHORT: 'Ekstenzija nije dozvoljena' + IcoType: Ikona + JpgType: 'JPEG slika - dobra za fotografije' + JsType: 'Javascript datoteka' + LASTEDIT: 'Zadnje uređeno' + MODIFIED: Uređeno + Mp3Type: 'MP3 audio datoteka' + MpgType: 'MPEG video datoteka' + NOFILESIZE: 'Veličina datoteke je nula bajtova,' + NOVALIDUPLOAD: 'Datoteka nije ispravno prenešena' + PATH: Putanja + PLURALNAME: Datoteke + PdfType: 'Adobe Acrobat PDF datoteka' + PngType: 'PNG slika - dobar sveopći format' + SINGULARNAME: oteka + TOOLARGE: 'Veličina datoteke je prevelika, maksimalno {size} dozvoljeno' + URL: URL + WavType: 'WAV audio datoteka' + XlsType: 'Excell tablica' + ZipType: 'ZIP komprimirana datoteka' + SilverStripe\Assets\Folder: + PLURALNAME: Direktoriji + SINGULARNAME: Direktor + SilverStripe\Assets\Image: + PLURALNAME: Slike + SINGULARNAME: Slika diff --git a/lang/id_ID.yml b/lang/id_ID.yml new file mode 100644 index 00000000..a2dacf81 --- /dev/null +++ b/lang/id_ID.yml @@ -0,0 +1,39 @@ +id_ID: + SilverStripe\Assets\File: + AviType: 'Berkas video AVI' + CREATED: 'Pertama upload' + CssType: 'Berkas CSS' + DRAFT: Draf + DocType: 'Dokumen Word' + EDIT_ALL_DESCRIPTION: 'Edit berkas lainnya' + GifType: 'Gambar GIF - bagus untuk diagram' + GzType: 'Berkas GZIP terkompresi' + HtmlType: 'Berkas HTML' + INVALIDEXTENSION_SHORT: 'Ekstensi tidak diperbolehkan' + IcoType: 'Gambar Icon' + JpgType: 'Gambar JPEG - bagus untuk foto' + JsType: 'Berkas Javascript' + LASTEDIT: 'Terakhir diubah' + MODIFIED: Dimodif + Mp3Type: 'Berkas suara MP3' + MpgType: 'Berkas video MPEG' + NOFILESIZE: 'Ukuran berkas kosong bit' + PLURALNAME: Berkas + PLURALS: + other: '{count} berkas' + PdfType: 'Berkas Adobe Acrobat PDF' + SINGULARNAME: Berkas + TOOLARGE: 'Ukuran berkas terlalu besar, maksimum {size} yang diizinkan' + URL: URL + WavType: 'Berkas suara WAV' + ZipType: 'Berkas ZIP Terkompresi' + SilverStripe\Assets\Folder: + PLURALNAME: Map + PLURALS: + other: '{count} Maps' + SINGULARNAME: Map + SilverStripe\Assets\Image: + PLURALNAME: Gambar + PLURALS: + other: '{count} Gambar' + SINGULARNAME: Gambar diff --git a/lang/it.yml b/lang/it.yml index 5fb05102..08817331 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -24,6 +24,7 @@ it: PLURALS: one: 'Un File' other: '{count} File' + many: '{count} File' PdfType: 'File PDF Adobe Acrobat' PngType: 'Immagine PNG - adatta per tutti gli usi' SINGULARNAME: File @@ -33,15 +34,19 @@ it: WavType: 'File audio WAV' XlsType: 'Foglio elettronico Excel' ZipType: 'File ZIP compresso' + EDIT_ALL_DESCRIPTION: 'Modifica ogni file' + EDIT_ALL_HELP: 'Modifica ogni file sul sito, anche se protetto' SilverStripe\Assets\Folder: PLURALNAME: Cartelle PLURALS: one: 'Una Cartella' other: '{count} Cartelle' + many: '{count} Cartelle' SINGULARNAME: Cartella SilverStripe\Assets\Image: PLURALNAME: Immagini PLURALS: one: 'Una Immagine' other: '{count} Immagini' + many: '{count} Immagini' SINGULARNAME: Immagine diff --git a/lang/pl.yml b/lang/pl.yml new file mode 100644 index 00000000..2882193a --- /dev/null +++ b/lang/pl.yml @@ -0,0 +1,55 @@ +pl: + SilverStripe\Assets\File: + AviType: 'Plik wideo .AVI' + CREATED: 'Wysłano po raz pierwszy' + CssType: 'Plik .CSS' + DRAFT: Szkic + DmgType: 'Obraz dysku Apple' + DocType: 'Plik Word' + EDIT_ALL_DESCRIPTION: 'Edytuj dowolny plik' + EDIT_ALL_HELP: 'Edytuj dowolny plik, nawet jeśli jest ograniczony' + GifType: 'Obraz GIF - dobry dla diagramów' + GzType: 'Plik skompresowany GZIP' + HtmlType: 'Plik HTML' + INVALIDEXTENSION_SHORT: 'To rozszerzenie nie jest dozwolone' + IcoType: 'Obraz ikony' + JpgType: 'Obraz JPEG - dobry do zdjęć' + JsType: 'Plik JavaScript' + LASTEDIT: 'Ostatnia zmiana' + MODIFIED: Zmodyfikowany + Mp3Type: 'Plik audio MP3' + MpgType: 'Plik wideo MPEG' + NOFILESIZE: 'Wielkość pliku wynosi zero bajtów.' + NOVALIDUPLOAD: 'Plik jest niepoprawny, nie można go przesłać' + PATH: Ścieżka + PLURALNAME: Pliki + PLURALS: + one: Plik + few: Plików + many: Plików + other: '{count} pliki' + PdfType: 'Plik .PDF' + PngType: 'Obraz PNG - dobry format ogólnego zastosowania' + SINGULARNAME: Plik + TOOLARGE: 'Wielkość pliku jest zbyt duża, {size} to maksymalna dozwolona wielkość' + TiffType: 'Oznaczony format obrazu' + URL: URL + WavType: 'Plik audio .WAV' + XlsType: 'Excel arkusz kalkulacyjny' + ZipType: 'Skompresowany plik .ZIP' + SilverStripe\Assets\Folder: + PLURALNAME: Foldery + PLURALS: + one: Folder + few: Folderów + many: Folderów + other: '{count} foldery' + SINGULARNAME: Folder + SilverStripe\Assets\Image: + PLURALNAME: Obrazy + PLURALS: + one: Obraz + few: obrazów + many: '{count} orazów' + other: '{count} orazów' + SINGULARNAME: Obraz diff --git a/lang/pl_PL.yml b/lang/pl_PL.yml new file mode 100644 index 00000000..502ddc37 --- /dev/null +++ b/lang/pl_PL.yml @@ -0,0 +1,55 @@ +pl_PL: + SilverStripe\Assets\File: + AviType: 'Plik wideo .AVI' + CREATED: 'Wysłano po raz pierwszy' + CssType: 'Plik .CSS' + DRAFT: Szkic + DmgType: 'Obraz dysku Apple' + DocType: 'Plik Word' + EDIT_ALL_DESCRIPTION: 'Edytuj dowolny plik' + EDIT_ALL_HELP: 'Edytuj dowolny plik, nawet jeśli jest ograniczony' + GifType: 'Obraz GIF - dobry dla diagramów' + GzType: 'Plik skompresowany GZIP' + HtmlType: 'Plik HTML' + INVALIDEXTENSION_SHORT: 'To rozszerzenie nie jest dozwolone' + IcoType: 'Obraz .iso' + JpgType: 'Obraz JPEG - dobry do zdjęć' + JsType: 'Plik JavaScript' + LASTEDIT: 'Ostatnia zmiana' + MODIFIED: Zmodyfikowany + Mp3Type: 'Plik audio MP3' + MpgType: 'Plik wideo MPEG' + NOFILESIZE: 'Plik jest pusty, wielkość pliku wynosi 0 bajtów.' + NOVALIDUPLOAD: 'Plik jest niepoprawny, nie można go przesłać' + PATH: Ścieżka + PLURALNAME: Pliki + PLURALS: + one: Plik + few: 'Plików {count}' + many: 'Plików {count}' + other: 'Pliki {count}' + PdfType: 'Plik .PDF' + PngType: 'Obraz PNG - dobry format ogólnego zastosowania' + SINGULARNAME: Plik + TOOLARGE: 'Wielkość pliku jest zbyt duża, {size} to maksymalna dozwolona wielkość' + TiffType: 'Plik .tiff' + URL: URL + WavType: 'Plik audio .WAV' + XlsType: 'Excel arkusz kalkulacyjny' + ZipType: 'Skompresowany plik .ZIP' + SilverStripe\Assets\Folder: + PLURALNAME: Foldery + PLURALS: + one: Folder + few: 'Folderów {count}' + many: 'Folderów {count}' + other: 'Foldery {count}' + SINGULARNAME: Folder + SilverStripe\Assets\Image: + PLURALNAME: Obrazki + PLURALS: + one: Obrazek + few: 'Obrazków {count}' + many: 'Obrazków {count}' + other: '{count} obrazów' + SINGULARNAME: Obrazek diff --git a/lang/sl.yml b/lang/sl.yml new file mode 100644 index 00000000..7bde2500 --- /dev/null +++ b/lang/sl.yml @@ -0,0 +1,55 @@ +sl: + SilverStripe\Assets\File: + AviType: 'Datoteka AVI' + CREATED: Ustvarjena + CssType: 'Datoteka CSS' + DRAFT: Osnutek + DmgType: 'Datoteka DMG' + DocType: 'Datoteka DOC' + EDIT_ALL_DESCRIPTION: 'Urejanje poljubne datoteke' + EDIT_ALL_HELP: 'Urejanje poljubne datoteke, četudi je zaščitena' + GifType: 'Datoteka GIF' + GzType: 'Datoteka GZIP' + HtmlType: 'Datoteka HTML' + INVALIDEXTENSION_SHORT: 'Nedovoljena pripona' + IcoType: 'Datoteka ICO' + JpgType: 'Datoteka JPEG' + JsType: 'Datoteka JavaScript' + LASTEDIT: 'Nazadnje spremenjena' + MODIFIED: Spremenjena + Mp3Type: 'Datoteka MP3' + MpgType: 'Datoteka MPEG' + NOFILESIZE: 'Velikost datoteke je nič.' + NOVALIDUPLOAD: 'Neveljaven tip datoteke' + PATH: Pot + PLURALNAME: Datoteke + PLURALS: + one: Datoteka + two: '{count} datoteki' + few: '{count} datotek' + other: '{count} datotek' + PdfType: 'Datoteka PDF' + PngType: 'Datoteka PNG' + SINGULARNAME: Datoteka + TOOLARGE: 'Datoteka je prevelika, največja še dovoljena velikost je {size}' + TiffType: 'Datoteka TIF' + URL: 'Naslov URL' + WavType: 'Datoteka WAV' + XlsType: 'Datoteka XLS' + ZipType: 'Datoteka ZIP' + SilverStripe\Assets\Folder: + PLURALNAME: Mape + PLURALS: + one: Mapa + two: '{count} mapi' + few: '{count} map' + other: '{count} map' + SINGULARNAME: Mapa + SilverStripe\Assets\Image: + PLURALNAME: Slike + PLURALS: + one: Slika + two: '{count} sliki' + few: '{count} slik' + other: '{count} slik' + SINGULARNAME: Slika diff --git a/lang/sv.yml b/lang/sv.yml new file mode 100644 index 00000000..c932f67e --- /dev/null +++ b/lang/sv.yml @@ -0,0 +1,48 @@ +sv: + SilverStripe\Assets\File: + AviType: 'AVI video-fil' + CREATED: Uppladdad + CssType: CSS-fil + DRAFT: Utkast + DmgType: 'Apple skivavbild' + DocType: Word-dokument + EDIT_ALL_DESCRIPTION: 'Ändra alla filer' + GifType: 'GIF-bild - bra för diagram' + GzType: 'GZIP-komprimerad fil' + HtmlType: HTML-fil + INVALIDEXTENSION_SHORT: 'Filändelse ej tillåten' + IcoType: Ikon-bild + JpgType: 'JPEG-bild - bra för fotografier' + JsType: Javascript-fil + LASTEDIT: 'Senast ändrad' + MODIFIED: Ändrad + Mp3Type: 'MP3 ljud-fil' + MpgType: 'MPEG video-fil' + NOFILESIZE: 'Filstorleken är 0 bytes' + NOVALIDUPLOAD: 'Filen är inte en giltig uppladdning' + PATH: Sökväg + PLURALNAME: Filer + PLURALS: + one: 'En fil' + other: '{count} filer' + PdfType: 'Adobe Acrobat PDF-fil' + PngType: 'PNG-bild - bra generellt format' + SINGULARNAME: Fil + TOOLARGE: 'Filen är för stor. Tillåten maxstorlek är {size}' + TiffType: 'Taggat bild-format' + URL: URL + WavType: 'WAV ljud-fil' + XlsType: Excel-fil + ZipType: 'ZIP komprimerad-fil' + SilverStripe\Assets\Folder: + PLURALNAME: Mappar + PLURALS: + one: 'En mapp' + other: '{count} mappar' + SINGULARNAME: Mapp + SilverStripe\Assets\Image: + PLURALNAME: Bilder + PLURALS: + one: 'En bild' + other: '{count} bilder' + SINGULARNAME: Bild From 8e50259031ca5b5d0252d4f1b15d347073524c4a Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Fri, 10 Mar 2023 12:21:26 +1300 Subject: [PATCH 19/32] MNT Update development dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 113f61c1..18374262 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": "^7.4 || ^8.0", - "silverstripe/framework": "^4.10", + "silverstripe/framework": "4.13.x-dev", "silverstripe/vendor-plugin": "^1.0", "symfony/filesystem": "^3.4|^4.0|^5.0", "intervention/image": "^2.7", @@ -50,4 +50,4 @@ }, "minimum-stability": "dev", "prefer-stable": true -} +} \ No newline at end of file From b46993cd07f1b8fc254aeca16421522bdfa23035 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Fri, 10 Mar 2023 15:50:59 +1300 Subject: [PATCH 20/32] MNT Update release dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 18374262..b84ac174 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": "^7.4 || ^8.0", - "silverstripe/framework": "4.13.x-dev", + "silverstripe/framework": "4.13.0-beta1", "silverstripe/vendor-plugin": "^1.0", "symfony/filesystem": "^3.4|^4.0|^5.0", "intervention/image": "^2.7", From 31a7f6a5fa8676bd0c870300f21a99a8cddacdbc Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Fri, 10 Mar 2023 15:51:03 +1300 Subject: [PATCH 21/32] MNT Update development dependencies --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b84ac174..18374262 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": "^7.4 || ^8.0", - "silverstripe/framework": "4.13.0-beta1", + "silverstripe/framework": "4.13.x-dev", "silverstripe/vendor-plugin": "^1.0", "symfony/filesystem": "^3.4|^4.0|^5.0", "intervention/image": "^2.7", From ddce6fcddc2e2dbc492abb9f1198c64bd9614a03 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Tue, 21 Mar 2023 12:20:32 +1300 Subject: [PATCH 22/32] MNT Use gha-dispatch-ci --- .github/workflows/ci.yml | 5 ----- .github/workflows/dispatch-ci.yml | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/dispatch-ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30e47fce..bf02210e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,13 +4,8 @@ on: push: pull_request: workflow_dispatch: - # Every Tuesday at 12:10pm UTC - schedule: - - cron: '10 12 * * 2' jobs: ci: name: CI - # Only run cron on the silverstripe account - if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule') uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1 diff --git a/.github/workflows/dispatch-ci.yml b/.github/workflows/dispatch-ci.yml new file mode 100644 index 00000000..9db0ff8e --- /dev/null +++ b/.github/workflows/dispatch-ci.yml @@ -0,0 +1,16 @@ +name: Dispatch CI + +on: + # At 12:10 PM UTC, only on Tuesday and Wednesday + schedule: + - cron: '10 12 * * 2,3' + +jobs: + dispatch-ci: + name: Dispatch CI + # Only run cron on the silverstripe account + if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule') + runs-on: ubuntu-latest + steps: + - name: Dispatch CI + uses: silverstripe/gha-dispatch-ci@v1 From fb2e4b69fa17b742a507360d4c9c260a32d88883 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> Date: Tue, 28 Mar 2023 16:47:21 +1300 Subject: [PATCH 23/32] MNT Revert erroneous dependency changes (#547) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 18374262..28768508 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": "^7.4 || ^8.0", - "silverstripe/framework": "4.13.x-dev", + "silverstripe/framework": "^4.10", "silverstripe/vendor-plugin": "^1.0", "symfony/filesystem": "^3.4|^4.0|^5.0", "intervention/image": "^2.7", From be8161b3436880bcdeebb97961c349c22124f2a9 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Wed, 19 Apr 2023 16:00:18 +1200 Subject: [PATCH 24/32] DOC Update README.md for CMS 5 --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 0cf14ad1..04916a75 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,12 @@ Required component of [Silverstripe Framework](https://github.com/silverstripe/silverstripe-framework) +## Installation + +```sh +composer require silverstripe/assets +``` + ## Links ## * [License](./LICENSE) From ae5b661dda468af9d0addb0afe490af6f0e06a7d Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Fri, 19 May 2023 11:49:31 +1200 Subject: [PATCH 25/32] FIX Use 0775 permissions for directories --- src/Flysystem/AssetAdapter.php | 6 +++++- tests/php/Flysystem/AssetAdapterTest.php | 26 +++++++++++++----------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Flysystem/AssetAdapter.php b/src/Flysystem/AssetAdapter.php index c5c4b1e6..41c6b8a2 100644 --- a/src/Flysystem/AssetAdapter.php +++ b/src/Flysystem/AssetAdapter.php @@ -39,6 +39,10 @@ class AssetAdapter extends LocalFilesystemAdapter /** * Config compatible permissions configuration * + * dir.private is defaulted to 0775 rather than 0700 to mimic the behaviour of CMS 4 where + * league/flysystem v1 would use the 'public' 0775 visibility for all directories, while still using + * the 'private' 0600 visibility for draft files + * * @config * @var array */ @@ -49,7 +53,7 @@ class AssetAdapter extends LocalFilesystemAdapter ], 'dir' => [ 'public' => 0775, - 'private' => 0700, + 'private' => 0775, ] ]; diff --git a/tests/php/Flysystem/AssetAdapterTest.php b/tests/php/Flysystem/AssetAdapterTest.php index 2206fadf..24635e61 100644 --- a/tests/php/Flysystem/AssetAdapterTest.php +++ b/tests/php/Flysystem/AssetAdapterTest.php @@ -20,18 +20,6 @@ class AssetAdapterTest extends SapphireTest protected function setUp(): void { parent::setUp(); - - AssetAdapter::config()->set('file_permissions', [ - 'file' => [ - 'public' => 0644, - 'private' => 0600, - ], - 'dir' => [ - 'public' => 0755, - 'private' => 0700, - ] - ]); - $this->rootDir = ASSETS_PATH . '/AssetAdapterTest'; Filesystem::makeFolder($this->rootDir); Config::modify()->set(Director::class, 'alternate_base_url', '/'); @@ -51,6 +39,20 @@ protected function tearDown(): void parent::tearDown(); } + public function testDefaultPermissions() + { + $this->assertSame(Config::inst()->get(AssetAdapter::class, 'file_permissions'), [ + 'file' => [ + 'public' => 0664, + 'private' => 0600, + ], + 'dir' => [ + 'public' => 0775, + 'private' => 0775, + ] + ]); + } + public function testPublicAdapter() { $_SERVER['SERVER_SOFTWARE'] = 'Apache/2.2.22 (Win64) PHP/5.3.13'; From 7c0cc54028ac818479e4d07b028617edc8b9dc33 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Tue, 30 May 2023 15:29:13 +1200 Subject: [PATCH 26/32] ENH Update translations --- lang/eo.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lang/eo.yml b/lang/eo.yml index e4956748..605d2230 100644 --- a/lang/eo.yml +++ b/lang/eo.yml @@ -35,6 +35,9 @@ eo: ZipType: 'kompaktigita ZIP-dosiero' EDIT_ALL_DESCRIPTION: 'Redakti ajnan dosieron' EDIT_ALL_HELP: 'Redakti ajnan dosieron en la retejo, eĉ se limigite alirebla' + INVALIDEXTENSION_SHORT_EXT: 'Sufikso ''{extension}'' ne estas permesita' + PARTIALUPLOAD: 'Dosiero ne tute alŝutiĝis. Bonvolu reprovi.' + PREVIEW: Antaŭvido SilverStripe\Assets\Folder: PLURALNAME: Dosierujoj PLURALS: @@ -47,3 +50,13 @@ eo: one: 'Unu bildo' other: '{count} bildoj' SINGULARNAME: Bildo + SilverStripe\Assets\ShortCodesImageShortcodeProvider: + IMAGENOTFOUND: 'Bildo ne troviĝis' + SilverStripe\Assets\Shortcodes\FileLink: + PLURALNAME: 'Dosieraj ligiloj' + PLURALS: + one: 'Unu dosiera ligilo' + other: '{count} dosieraj ligiloj' + SINGULARNAME: 'Dosiera ligilo' + SilverStripe\Assets\Shortcodes\ImageShortcodeProvider: + IMAGENOTFOUND: 'Bildo ne troviĝis' From 05732c00ff0b1bd662db8feaef4f41b979280951 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Wed, 14 Jun 2023 12:40:35 +1200 Subject: [PATCH 27/32] ENH Update translations --- lang/en.yml | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lang/en.yml b/lang/en.yml index 29094d0d..9541bfb6 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -12,7 +12,7 @@ en: GzType: 'GZIP compressed file' HtmlType: 'HTML file' INVALIDEXTENSION_SHORT: 'Extension is not allowed' - INVALIDEXTENSION_SHORT_EXT: 'Extension ''{extension}'' is not allowed' + INVALIDEXTENSION_SHORT_EXT: "Extension '{extension}' is not allowed" IcoType: 'Icon image' JpgType: 'JPEG image - good for photos' JsType: 'Javascript file' @@ -38,6 +38,25 @@ en: WavType: 'WAV audo file' XlsType: 'Excel spreadsheet' ZipType: 'ZIP compressed file' + belongs_to_SubmittedFileField: 'Submitted file field' + db_CanEditType: 'Can edit type' + db_CanViewType: 'Can view type' + db_File: File + db_FileContentCache: 'File content cache' + db_Name: Name + db_ShowInSearch: 'Show in search' + db_Title: Title + db_UserFormUpload: 'User form upload' + db_Version: Version + has_many_BackLinks: 'Back links' + has_one_BasicFieldsTestPage: 'Basic fields test page' + has_one_Company: Company + has_one_Owner: Owner + has_one_Parent: Parent + has_one_Subsite: Subsite + has_one_TestPage: 'Test page' + many_many_EditorGroups: 'Editor groups' + many_many_ViewerGroups: 'Viewer groups' SilverStripe\Assets\Folder: PLURALNAME: Folders PLURALS: @@ -58,5 +77,9 @@ en: one: 'A File Link' other: '{count} File Links' SINGULARNAME: 'File Link' + has_one_Linked: Linked + has_one_Parent: Parent + SilverStripe\Assets\Shortcodes\FileLinkTracking: + many_many_FileTracking: 'File tracking' SilverStripe\Assets\Shortcodes\ImageShortcodeProvider: IMAGENOTFOUND: 'Image not found' From f83706eb1f864c74357f9b1a5405a8dab3a5054f Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Wed, 14 Jun 2023 18:35:43 +1200 Subject: [PATCH 28/32] ENH Update translations --- lang/bg.yml | 1 + lang/da.yml | 1 + lang/de.yml | 1 + lang/en.yml | 23 +++++++++++++++++++++++ lang/eo.yml | 1 + lang/fi.yml | 1 + lang/fi_FI.yml | 1 + lang/fr.yml | 1 + lang/hr.yml | 1 + lang/id_ID.yml | 1 + lang/it.yml | 1 + lang/nl.yml | 1 + lang/pl.yml | 1 + lang/pl_PL.yml | 1 + lang/ru.yml | 1 + lang/sl.yml | 1 + lang/sv.yml | 1 + 17 files changed, 39 insertions(+) diff --git a/lang/bg.yml b/lang/bg.yml index 6260f74f..b6e8c0a3 100644 --- a/lang/bg.yml +++ b/lang/bg.yml @@ -32,6 +32,7 @@ bg: WavType: 'WAV аудио' XlsType: 'Excel таблица' ZipType: 'Компресиран файл ZIP' + db_File: Файл SilverStripe\Assets\Folder: PLURALNAME: Папки PLURALS: diff --git a/lang/da.yml b/lang/da.yml index 0ad28663..598f40c6 100644 --- a/lang/da.yml +++ b/lang/da.yml @@ -35,6 +35,7 @@ da: WavType: 'WAV lyd fil' XlsType: 'Excel regneark' ZipType: 'ZIP komprimeret fil' + db_File: Fil SilverStripe\Assets\Folder: PLURALNAME: Mapper PLURALS: diff --git a/lang/de.yml b/lang/de.yml index d83eb50c..30453ecb 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -35,6 +35,7 @@ de: EDIT_ALL_HELP: 'Alle, auch eingeschränkte, Dateien bearbeiten' NOVALIDUPLOAD: 'Die Datei ist kein gültiger Upload' TiffType: 'Tagged Image File Format' + db_File: Datei SilverStripe\Assets\Folder: PLURALNAME: Ordner PLURALS: diff --git a/lang/en.yml b/lang/en.yml index 29094d0d..823e73a9 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -38,6 +38,25 @@ en: WavType: 'WAV audo file' XlsType: 'Excel spreadsheet' ZipType: 'ZIP compressed file' + belongs_to_SubmittedFileField: 'Submitted file field' + db_CanEditType: 'Can edit type' + db_CanViewType: 'Can view type' + db_File: File + db_FileContentCache: 'File content cache' + db_Name: Name + db_ShowInSearch: 'Show in search' + db_Title: Title + db_UserFormUpload: 'User form upload' + db_Version: Version + has_many_BackLinks: 'Back links' + has_one_BasicFieldsTestPage: 'Basic fields test page' + has_one_Company: Company + has_one_Owner: Owner + has_one_Parent: Parent + has_one_Subsite: Subsite + has_one_TestPage: 'Test page' + many_many_EditorGroups: 'Editor groups' + many_many_ViewerGroups: 'Viewer groups' SilverStripe\Assets\Folder: PLURALNAME: Folders PLURALS: @@ -58,5 +77,9 @@ en: one: 'A File Link' other: '{count} File Links' SINGULARNAME: 'File Link' + has_one_Linked: Linked + has_one_Parent: Parent SilverStripe\Assets\Shortcodes\ImageShortcodeProvider: IMAGENOTFOUND: 'Image not found' + SilverStripe\Assets\Shortcodes\FileLinkTracking: + many_many_FileTracking: 'File tracking' diff --git a/lang/eo.yml b/lang/eo.yml index 605d2230..90452a8b 100644 --- a/lang/eo.yml +++ b/lang/eo.yml @@ -38,6 +38,7 @@ eo: INVALIDEXTENSION_SHORT_EXT: 'Sufikso ''{extension}'' ne estas permesita' PARTIALUPLOAD: 'Dosiero ne tute alŝutiĝis. Bonvolu reprovi.' PREVIEW: Antaŭvido + db_File: Dosiero SilverStripe\Assets\Folder: PLURALNAME: Dosierujoj PLURALS: diff --git a/lang/fi.yml b/lang/fi.yml index 898f5f03..ca17e251 100644 --- a/lang/fi.yml +++ b/lang/fi.yml @@ -35,6 +35,7 @@ fi: WavType: WAV-äänitiedosto XlsType: Excel-laskentataulukko ZipType: 'ZIP-pakattu tiedosto' + db_File: Tiedosto SilverStripe\Assets\Folder: PLURALNAME: Kansiot PLURALS: diff --git a/lang/fi_FI.yml b/lang/fi_FI.yml index c1d891fa..0c19e9a6 100644 --- a/lang/fi_FI.yml +++ b/lang/fi_FI.yml @@ -35,6 +35,7 @@ fi_FI: WavType: WAV-äänitiedosto XlsType: Excel-laskentataulukko ZipType: 'ZIP-pakattu tiedosto' + db_File: Tiedosto SilverStripe\Assets\Folder: PLURALNAME: Kansiot PLURALS: diff --git a/lang/fr.yml b/lang/fr.yml index 1a11acc8..a7699da7 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -36,6 +36,7 @@ fr: WavType: 'Fichier audio WAV' XlsType: 'Tableur Excel' ZipType: 'Fichier compressé ZIP' + db_File: Fichier SilverStripe\Assets\Folder: PLURALNAME: Dossiers PLURALS: diff --git a/lang/hr.yml b/lang/hr.yml index 7bde362c..8c67df5b 100644 --- a/lang/hr.yml +++ b/lang/hr.yml @@ -31,6 +31,7 @@ hr: WavType: 'WAV audio datoteka' XlsType: 'Excell tablica' ZipType: 'ZIP komprimirana datoteka' + db_File: oteka SilverStripe\Assets\Folder: PLURALNAME: Direktoriji SINGULARNAME: Direktor diff --git a/lang/id_ID.yml b/lang/id_ID.yml index a2dacf81..d5b3cfe6 100644 --- a/lang/id_ID.yml +++ b/lang/id_ID.yml @@ -27,6 +27,7 @@ id_ID: URL: URL WavType: 'Berkas suara WAV' ZipType: 'Berkas ZIP Terkompresi' + db_File: Berkas SilverStripe\Assets\Folder: PLURALNAME: Map PLURALS: diff --git a/lang/it.yml b/lang/it.yml index 08817331..12da1252 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -36,6 +36,7 @@ it: ZipType: 'File ZIP compresso' EDIT_ALL_DESCRIPTION: 'Modifica ogni file' EDIT_ALL_HELP: 'Modifica ogni file sul sito, anche se protetto' + db_File: File SilverStripe\Assets\Folder: PLURALNAME: Cartelle PLURALS: diff --git a/lang/nl.yml b/lang/nl.yml index a8cd7679..57852bcd 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -35,6 +35,7 @@ nl: WavType: 'WAV audio bestand' XlsType: 'Excel spreadsheet' ZipType: 'ZIP gecomprimeerd bestand' + db_File: Bestand SilverStripe\Assets\Folder: PLURALNAME: Mappen PLURALS: diff --git a/lang/pl.yml b/lang/pl.yml index 2882193a..71fae473 100644 --- a/lang/pl.yml +++ b/lang/pl.yml @@ -37,6 +37,7 @@ pl: WavType: 'Plik audio .WAV' XlsType: 'Excel arkusz kalkulacyjny' ZipType: 'Skompresowany plik .ZIP' + db_File: Plik SilverStripe\Assets\Folder: PLURALNAME: Foldery PLURALS: diff --git a/lang/pl_PL.yml b/lang/pl_PL.yml index 502ddc37..e7644b82 100644 --- a/lang/pl_PL.yml +++ b/lang/pl_PL.yml @@ -37,6 +37,7 @@ pl_PL: WavType: 'Plik audio .WAV' XlsType: 'Excel arkusz kalkulacyjny' ZipType: 'Skompresowany plik .ZIP' + db_File: Plik SilverStripe\Assets\Folder: PLURALNAME: Foldery PLURALS: diff --git a/lang/ru.yml b/lang/ru.yml index 7b72a8bc..e475429d 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -35,6 +35,7 @@ ru: WavType: 'WAV аудиофайл' XlsType: 'Таблица Excel' ZipType: 'ZIP архив' + db_File: Файл SilverStripe\Assets\Folder: PLURALNAME: Папки PLURALS: diff --git a/lang/sl.yml b/lang/sl.yml index 7bde2500..a1895c19 100644 --- a/lang/sl.yml +++ b/lang/sl.yml @@ -37,6 +37,7 @@ sl: WavType: 'Datoteka WAV' XlsType: 'Datoteka XLS' ZipType: 'Datoteka ZIP' + db_File: Datoteka SilverStripe\Assets\Folder: PLURALNAME: Mape PLURALS: diff --git a/lang/sv.yml b/lang/sv.yml index c932f67e..10d35cc6 100644 --- a/lang/sv.yml +++ b/lang/sv.yml @@ -34,6 +34,7 @@ sv: WavType: 'WAV ljud-fil' XlsType: Excel-fil ZipType: 'ZIP komprimerad-fil' + db_File: Fil SilverStripe\Assets\Folder: PLURALNAME: Mappar PLURALS: From 47fa6c648163ad40f6ad585fde98fc132c37a2e9 Mon Sep 17 00:00:00 2001 From: Andrew Paxley Date: Thu, 15 Jun 2023 12:09:35 +1200 Subject: [PATCH 29/32] ENH add check for specific user inherited permission --- composer.json | 2 +- src/File.php | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 3694f3a5..dd3b7a97 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": "^8.1", - "silverstripe/framework": "^5", + "silverstripe/framework": "^5.1", "silverstripe/vendor-plugin": "^2", "symfony/filesystem": "^6.1", "intervention/image": "^2.7.2", diff --git a/src/File.php b/src/File.php index a6dfb855..017ca8fc 100644 --- a/src/File.php +++ b/src/File.php @@ -397,6 +397,14 @@ public function canView($member = null) return $member->inGroups($this->ViewerGroups()); } + // Specific users can view this file + if ($this->CanViewType === InheritedPermissions::ONLY_THESE_MEMBERS) { + if (!$member) { + return false; + } + return $this->ViewerMembers()->filter('ID', $member->ID)->count() > 0; + } + // Check default root level permissions return $this->getPermissionChecker()->canView($this->ID, $member); } @@ -423,7 +431,7 @@ public function canEdit($member = null) } // Delegate to parent if inheriting permissions - if ($this->CanEditType === 'Inherit' && $this->ParentID) { + if ($this->CanEditType === InheritedPermissions::INHERIT && $this->ParentID) { return $this->getPermissionChecker()->canEdit($this->ParentID, $member); } @@ -518,7 +526,11 @@ private function hasRestrictedPermissions(File $file): bool $id = $file->ID; $parentID = $file->ParentID; $canViewType = $file->CanViewType; - if (in_array($canViewType, [InheritedPermissions::LOGGED_IN_USERS, InheritedPermissions::ONLY_THESE_USERS])) { + if (in_array($canViewType, [ + InheritedPermissions::LOGGED_IN_USERS, + InheritedPermissions::ONLY_THESE_USERS, + InheritedPermissions::ONLY_THESE_MEMBERS, + ])) { self::$has_restricted_permissions_cache[$id] = true; return true; } From bd9ebb72a0fb91ed2ca3d977f5b88efd6d0f7740 Mon Sep 17 00:00:00 2001 From: Sabina Talipova Date: Tue, 11 Jul 2023 12:37:36 +1200 Subject: [PATCH 30/32] FIX getCachedMarkup returns NULL instead of string if file doesn't exist --- src/Shortcodes/FileShortcodeProvider.php | 8 +++-- .../Shortcodes/FileShortcodeProviderTest.php | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Shortcodes/FileShortcodeProvider.php b/src/Shortcodes/FileShortcodeProvider.php index 29cf9b62..42feb332 100644 --- a/src/Shortcodes/FileShortcodeProvider.php +++ b/src/Shortcodes/FileShortcodeProvider.php @@ -139,11 +139,15 @@ public static function handle_shortcode($arguments, $content, $parser, $shortcod protected static function getCachedMarkup($cache, $cacheKey, $arguments): string { $item = $cache->get($cacheKey); - if ($item && !empty($item['filename'])) { + $assetStore = Injector::inst()->get(AssetStore::class); + if ($item + && !empty($item['filename']) + && $assetStore->exists($item['filename'], $item['hash']) + && $item['markup']) { // Initiate a protected asset grant if necessary $allowSessionGrant = static::getGrant(null, $arguments); if ($allowSessionGrant) { - Injector::inst()->get(AssetStore::class)->grant($item['filename'], $item['hash']); + $assetStore->grant($item['filename'], $item['hash']); return $item['markup']; } } diff --git a/tests/php/Shortcodes/FileShortcodeProviderTest.php b/tests/php/Shortcodes/FileShortcodeProviderTest.php index 90c79c5e..a671c49c 100644 --- a/tests/php/Shortcodes/FileShortcodeProviderTest.php +++ b/tests/php/Shortcodes/FileShortcodeProviderTest.php @@ -149,4 +149,37 @@ public function testOnlyGrantsAccessWhenConfiguredTo() $parser->parse(sprintf('[file_link,id=%d]', $testFile->ID)); $this->assertFalse($assetStore->isGranted($testFile)); } + + public function testMarkupHasStringValue() + { + $testFile = $this->objFromFixture(File::class, 'asdf'); + $testFileID = $testFile->ID; + $tuple = $testFile->File->getValue(); + + $assetStore = Injector::inst()->get(AssetStore::class); + + $parser = new ShortcodeParser(); + $parser->register('file_link', [FileShortcodeProvider::class, 'handle_shortcode']); + + FileShortcodeProvider::config()->set('allow_session_grant', true); + + $fileShortcode = sprintf('[file_link,id=%d]', $testFileID); + $this->assertEquals( + $testFile->Link(), + $parser->parse(sprintf('[file_link,id=%d]', $testFileID)), + 'Test that shortcode with existing file ID is parsed.' + ); + + $testFile->deleteFile(); + $this->assertFalse( + $assetStore->exists($tuple['Filename'], $tuple['Hash']), + 'Test that file was removed from Asset store.' + ); + + $this->assertEquals( + '', + $parser->parse(sprintf('[file_link,id=%d]', $testFileID)), + 'Test that shortcode with invalid file ID is not parsed.' + ); + } } From 69cc7edae15165ed5051d7587ac5219f84ec8e58 Mon Sep 17 00:00:00 2001 From: Sabina Talipova Date: Tue, 11 Jul 2023 14:28:18 +1200 Subject: [PATCH 31/32] FIX --- src/Shortcodes/FileShortcodeProvider.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Shortcodes/FileShortcodeProvider.php b/src/Shortcodes/FileShortcodeProvider.php index 42feb332..60af995a 100644 --- a/src/Shortcodes/FileShortcodeProvider.php +++ b/src/Shortcodes/FileShortcodeProvider.php @@ -140,13 +140,10 @@ protected static function getCachedMarkup($cache, $cacheKey, $arguments): string { $item = $cache->get($cacheKey); $assetStore = Injector::inst()->get(AssetStore::class); - if ($item - && !empty($item['filename']) - && $assetStore->exists($item['filename'], $item['hash']) - && $item['markup']) { + if ($item && !empty($item['filename']) && $item['markup']) { // Initiate a protected asset grant if necessary $allowSessionGrant = static::getGrant(null, $arguments); - if ($allowSessionGrant) { + if ($allowSessionGrant && $assetStore->exists($item['filename'], $item['hash'])) { $assetStore->grant($item['filename'], $item['hash']); return $item['markup']; } From 7f8057ea9af1a11fc8e4dbc3e24fdae84f97dc8b Mon Sep 17 00:00:00 2001 From: Sabina Talipova Date: Tue, 11 Jul 2023 14:29:48 +1200 Subject: [PATCH 32/32] FIX --- src/Shortcodes/FileShortcodeProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shortcodes/FileShortcodeProvider.php b/src/Shortcodes/FileShortcodeProvider.php index 60af995a..30326a71 100644 --- a/src/Shortcodes/FileShortcodeProvider.php +++ b/src/Shortcodes/FileShortcodeProvider.php @@ -140,7 +140,7 @@ protected static function getCachedMarkup($cache, $cacheKey, $arguments): string { $item = $cache->get($cacheKey); $assetStore = Injector::inst()->get(AssetStore::class); - if ($item && !empty($item['filename']) && $item['markup']) { + if ($item && $item['markup'] && !empty($item['filename'])) { // Initiate a protected asset grant if necessary $allowSessionGrant = static::getGrant(null, $arguments); if ($allowSessionGrant && $assetStore->exists($item['filename'], $item['hash'])) {