From dae6d5902f7968222a1ad8b3f879fbf4ab2b2f7a Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 16 Mar 2017 16:47:26 +1300 Subject: [PATCH] API Split SilverStripe\Assets into separate module --- .travis.yml | 6 +- .upgrade.yml | 58 - _config/asset.yml | 68 - _config/model.yml | 2 - composer.json | 3 +- phpunit.xml.dist | 3 +- src/Assets/AssetControlExtension.php | 304 ---- src/Assets/AssetManipulationList.php | 175 --- src/Assets/File.php | 1320 ----------------- src/Assets/FileFinder.php | 278 ---- src/Assets/FileMigrationHelper.php | 127 -- src/Assets/FileNameFilter.php | 140 -- src/Assets/Filesystem.php | 183 --- src/Assets/Flysystem/AssetAdapter.php | 153 -- src/Assets/Flysystem/FlysystemAssetStore.php | 910 ------------ .../Flysystem/GeneratedAssetHandler.php | 116 -- src/Assets/Flysystem/ProtectedAdapter.php | 20 - .../Flysystem/ProtectedAssetAdapter.php | 59 - src/Assets/Flysystem/PublicAdapter.php | 20 - src/Assets/Flysystem/PublicAssetAdapter.php | 60 - src/Assets/Folder.php | 355 ----- src/Assets/GDBackend.php | 767 ---------- src/Assets/Image.php | 255 ---- src/Assets/ImageManipulation.php | 828 ----------- src/Assets/Image_Backend.php | 151 -- src/Assets/ImagickBackend.php | 285 ---- src/Assets/Storage/AssetContainer.php | 187 --- src/Assets/Storage/AssetNameGenerator.php | 27 - src/Assets/Storage/AssetStore.php | 267 ---- src/Assets/Storage/AssetStoreRouter.php | 23 - src/Assets/Storage/DBFile.php | 563 ------- .../Storage/DefaultAssetNameGenerator.php | 170 --- src/Assets/Storage/GeneratedAssetHandler.php | 53 - .../Storage/ProtectedFileController.php | 100 -- src/Assets/Thumbnail.php | 19 - src/Assets/Upload.php | 437 ------ src/Assets/Upload_Validator.php | 313 ---- src/Core/Extensible.php | 2 +- src/ORM/DataObject.php | 12 - templates/DBFile_download.ss | 1 - templates/DBFile_image.ss | 1 - templates/Image_iframe.ss | 28 - .../ProtectedAssetAdapter_HTAccess.ss | 2 - .../ProtectedAssetAdapter_WebConfig.ss | 17 - .../Flysystem/PublicAssetAdapter_HTAccess.ss | 27 - .../Flysystem/PublicAssetAdapter_WebConfig.ss | 29 - .../php/Assets/AssetControlExtensionTest.php | 230 --- .../ArchivedObject.php | 15 - .../AssetControlExtensionTest/TestObject.php | 33 - .../VersionedObject.php | 52 - .../php/Assets/AssetManipulationListTest.php | 86 -- tests/php/Assets/FileFinderTest.php | 136 -- .../Assets/FileFinderTest/dir1/dir1file1.txt | 0 .../Assets/FileFinderTest/dir1/dir1file2.txt | 0 .../FileFinderTest/dir1/dir2/dir2file1.txt | 0 .../dir1/dir2/dir3/dir3file1.txt | 0 tests/php/Assets/FileFinderTest/file1.txt | 0 tests/php/Assets/FileFinderTest/file2.txt | 0 tests/php/Assets/FileMigrationHelperTest.php | 101 -- tests/php/Assets/FileMigrationHelperTest.yml | 21 - .../FileMigrationHelperTest/Extension.php | 26 - tests/php/Assets/FileNameFilterTest.php | 152 -- tests/php/Assets/FileTest.php | 714 --------- tests/php/Assets/FileTest.yml | 84 -- tests/php/Assets/FileTest/MyCustomFile.php | 11 - .../php/Assets/Flysystem/AssetAdapterTest.php | 80 - tests/php/Assets/FolderTest.php | 302 ---- tests/php/Assets/GDTest.php | 221 --- tests/php/Assets/GDTest/ImageUnavailable.php | 15 - .../php/Assets/GDTest/images/nonimagedata.jpg | 1 - tests/php/Assets/GDTest/images/test_gif.gif | Bin 132 -> 0 bytes tests/php/Assets/GDTest/images/test_jpg.jpg | Bin 8081 -> 0 bytes tests/php/Assets/GDTest/images/test_png32.png | Bin 240 -> 0 bytes tests/php/Assets/GDTest/images/test_png8.png | Bin 253 -> 0 bytes .../Assets/ProtectedFileControllerTest.php | 224 --- tests/php/Assets/Storage/AssetStoreTest.php | 523 ------- .../Storage/AssetStoreTest/TestAssetStore.php | 183 --- .../Storage/DefaultAssetNameGeneratorTest.php | 135 -- tests/php/Assets/UploadTest.php | 850 ----------- tests/php/Assets/UploadTest/Validator.php | 44 - tests/php/Forms/DBFileTest.php | 101 -- tests/php/Forms/DBFileTest/ImageOnly.php | 19 - tests/php/Forms/DBFileTest/Subclass.php | 18 - tests/php/Forms/DBFileTest/TestObject.php | 19 - tests/php/ORM/DataDifferencerTest.php | 3 +- .../images}/test-image-high-quality.jpg | Bin .../images}/test-image-low-quality.jpg | Bin .../images}/test-image.png | Bin .../images}/test.image.with.dots.png | Bin tests/php/ORM/GDImageTest.php | 40 - tests/php/ORM/ImageTest.php | 328 ---- tests/php/ORM/ImageTest.yml | 38 - tests/php/ORM/ImagickImageTest.php | 25 - 93 files changed, 7 insertions(+), 13747 deletions(-) delete mode 100644 src/Assets/AssetControlExtension.php delete mode 100644 src/Assets/AssetManipulationList.php delete mode 100644 src/Assets/File.php delete mode 100644 src/Assets/FileFinder.php delete mode 100644 src/Assets/FileMigrationHelper.php delete mode 100644 src/Assets/FileNameFilter.php delete mode 100644 src/Assets/Filesystem.php delete mode 100644 src/Assets/Flysystem/AssetAdapter.php delete mode 100644 src/Assets/Flysystem/FlysystemAssetStore.php delete mode 100644 src/Assets/Flysystem/GeneratedAssetHandler.php delete mode 100644 src/Assets/Flysystem/ProtectedAdapter.php delete mode 100644 src/Assets/Flysystem/ProtectedAssetAdapter.php delete mode 100644 src/Assets/Flysystem/PublicAdapter.php delete mode 100644 src/Assets/Flysystem/PublicAssetAdapter.php delete mode 100644 src/Assets/Folder.php delete mode 100644 src/Assets/GDBackend.php delete mode 100644 src/Assets/Image.php delete mode 100644 src/Assets/ImageManipulation.php delete mode 100644 src/Assets/Image_Backend.php delete mode 100644 src/Assets/ImagickBackend.php delete mode 100644 src/Assets/Storage/AssetContainer.php delete mode 100644 src/Assets/Storage/AssetNameGenerator.php delete mode 100644 src/Assets/Storage/AssetStore.php delete mode 100644 src/Assets/Storage/AssetStoreRouter.php delete mode 100644 src/Assets/Storage/DBFile.php delete mode 100644 src/Assets/Storage/DefaultAssetNameGenerator.php delete mode 100644 src/Assets/Storage/GeneratedAssetHandler.php delete mode 100644 src/Assets/Storage/ProtectedFileController.php delete mode 100644 src/Assets/Thumbnail.php delete mode 100644 src/Assets/Upload.php delete mode 100644 src/Assets/Upload_Validator.php delete mode 100644 templates/DBFile_download.ss delete mode 100644 templates/DBFile_image.ss delete mode 100644 templates/Image_iframe.ss delete mode 100644 templates/SilverStripe/Assets/Flysystem/ProtectedAssetAdapter_HTAccess.ss delete mode 100644 templates/SilverStripe/Assets/Flysystem/ProtectedAssetAdapter_WebConfig.ss delete mode 100644 templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_HTAccess.ss delete mode 100644 templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_WebConfig.ss delete mode 100644 tests/php/Assets/AssetControlExtensionTest.php delete mode 100644 tests/php/Assets/AssetControlExtensionTest/ArchivedObject.php delete mode 100644 tests/php/Assets/AssetControlExtensionTest/TestObject.php delete mode 100644 tests/php/Assets/AssetControlExtensionTest/VersionedObject.php delete mode 100644 tests/php/Assets/AssetManipulationListTest.php delete mode 100644 tests/php/Assets/FileFinderTest.php delete mode 100644 tests/php/Assets/FileFinderTest/dir1/dir1file1.txt delete mode 100644 tests/php/Assets/FileFinderTest/dir1/dir1file2.txt delete mode 100644 tests/php/Assets/FileFinderTest/dir1/dir2/dir2file1.txt delete mode 100644 tests/php/Assets/FileFinderTest/dir1/dir2/dir3/dir3file1.txt delete mode 100644 tests/php/Assets/FileFinderTest/file1.txt delete mode 100644 tests/php/Assets/FileFinderTest/file2.txt delete mode 100644 tests/php/Assets/FileMigrationHelperTest.php delete mode 100644 tests/php/Assets/FileMigrationHelperTest.yml delete mode 100644 tests/php/Assets/FileMigrationHelperTest/Extension.php delete mode 100644 tests/php/Assets/FileNameFilterTest.php delete mode 100644 tests/php/Assets/FileTest.php delete mode 100644 tests/php/Assets/FileTest.yml delete mode 100644 tests/php/Assets/FileTest/MyCustomFile.php delete mode 100644 tests/php/Assets/Flysystem/AssetAdapterTest.php delete mode 100644 tests/php/Assets/FolderTest.php delete mode 100644 tests/php/Assets/GDTest.php delete mode 100644 tests/php/Assets/GDTest/ImageUnavailable.php delete mode 100644 tests/php/Assets/GDTest/images/nonimagedata.jpg delete mode 100644 tests/php/Assets/GDTest/images/test_gif.gif delete mode 100644 tests/php/Assets/GDTest/images/test_jpg.jpg delete mode 100644 tests/php/Assets/GDTest/images/test_png32.png delete mode 100644 tests/php/Assets/GDTest/images/test_png8.png delete mode 100644 tests/php/Assets/ProtectedFileControllerTest.php delete mode 100644 tests/php/Assets/Storage/AssetStoreTest.php delete mode 100644 tests/php/Assets/Storage/AssetStoreTest/TestAssetStore.php delete mode 100644 tests/php/Assets/Storage/DefaultAssetNameGeneratorTest.php delete mode 100644 tests/php/Assets/UploadTest.php delete mode 100644 tests/php/Assets/UploadTest/Validator.php delete mode 100644 tests/php/Forms/DBFileTest.php delete mode 100644 tests/php/Forms/DBFileTest/ImageOnly.php delete mode 100644 tests/php/Forms/DBFileTest/Subclass.php delete mode 100644 tests/php/Forms/DBFileTest/TestObject.php rename tests/php/ORM/{ImageTest => DataDifferencerTest/images}/test-image-high-quality.jpg (100%) rename tests/php/ORM/{ImageTest => DataDifferencerTest/images}/test-image-low-quality.jpg (100%) rename tests/php/ORM/{ImageTest => DataDifferencerTest/images}/test-image.png (100%) rename tests/php/ORM/{ImageTest => DataDifferencerTest/images}/test.image.with.dots.png (100%) delete mode 100644 tests/php/ORM/GDImageTest.php delete mode 100644 tests/php/ORM/ImageTest.php delete mode 100644 tests/php/ORM/ImageTest.yml delete mode 100644 tests/php/ORM/ImagickImageTest.php diff --git a/.travis.yml b/.travis.yml index a8727f97a..f76747ef9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ before_script: - composer install --prefer-dist - "if [ \"$DB\" = \"PGSQL\" ]; then composer require silverstripe/postgresql:2.0.x-dev --prefer-dist; fi" - "if [ \"$DB\" = \"SQLITE\" ]; then composer require silverstripe/sqlite3:2.0.x-dev --prefer-dist; fi" - - composer require silverstripe/config:1.0.x-dev silverstripe/admin:1.0.x-dev --prefer-dist + - composer require silverstripe/config:1.0.x-dev silverstripe/admin:1.0.x-dev silverstripe/assets:1.0.x-dev --prefer-dist - "if [ \"$CMS_TEST\" = \"1\" ]; then composer require silverstripe/cms:4.0.x-dev silverstripe/siteconfig:4.0.x-dev silverstripe/reports:4.0.x-dev --prefer-dist; fi" - "if [ \"$CMS_TEST\" = \"1\" ]; then php ./cms/tests/bootstrap/mysite.php; fi" - "if [ \"$BEHAT_TEST\" = \"1\" ]; then sh -e /etc/init.d/xvfb start; sleep 3; fi" @@ -49,14 +49,14 @@ before_script: - "if [ \"$BEHAT_TEST\" = \"1\" ] && [ \"$CMS_TEST\" = \"1\" ]; then (vendor/bin/serve --bootstrap-file cms/tests/behat/serve-bootstrap.php &> serve.log &); fi" script: - - "if [ \"$PHPUNIT_TEST\" = \"1\" ] && [ \"$CMS_TEST\" = \"\" ]; then vendor/bin/phpunit tests/php; fi" + - "if [ \"$PHPUNIT_TEST\" = \"1\" ] && [ \"$CMS_TEST\" = \"\" ]; then vendor/bin/phpunit; fi" + - "if [ \"$PHPUNIT_COVERAGE_TEST\" = \"1\" ] && [ \"$CMS_TEST\" = \"\" ]; then phpdbg -qrr ./vendor/bin/phpunit --coverage-clover=coverage.xml; fi" - "if [ \"$PHPUNIT_TEST\" = \"1\" ] && [ \"$CMS_TEST\" = \"1\" ]; then vendor/bin/phpunit cms/tests; fi" - "if [ \"$BEHAT_TEST\" = \"1\" ] && [ \"$CMS_TEST\" = \"\" ]; then vendor/bin/behat --config tests/behat/config.yml .; fi" - "if [ \"$BEHAT_TEST\" = \"1\" ] && [ \"$CMS_TEST\" = \"1\" ]; then vendor/bin/behat @cms --config tests/behat/cms-config.yml; fi" - "if [ \"$PHPCS_TEST\" = \"1\" ]; then composer run-script lint; fi" after_success: - - "if [ \"$PHPUNIT_COVERAGE_TEST\" = \"1\" ] && [ \"$CMS_TEST\" = \"\" ]; then phpdbg -qrr ./vendor/bin/phpunit --coverage-clover=coverage.xml; fi" - "if [ \"$PHPUNIT_COVERAGE_TEST\" = \"1\" ] && [ \"$CMS_TEST\" = \"\" ]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi" after_failure: diff --git a/.upgrade.yml b/.upgrade.yml index 6d1db7c39..678f3e546 100644 --- a/.upgrade.yml +++ b/.upgrade.yml @@ -182,38 +182,6 @@ mappings: MemberImportForm: SilverStripe\Admin\MemberImportForm ModelAdmin: SilverStripe\Admin\ModelAdmin SecurityAdmin: SilverStripe\Admin\SecurityAdmin - SilverStripe\Filesystem\Storage\AssetContainer: SilverStripe\Assets\Storage\AssetContainer - SilverStripe\Filesystem\Storage\AssetNameGenerator: SilverStripe\Assets\Storage\AssetNameGenerator - 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 - SilverStripe\Filesystem\Storage\GeneratedAssetHandler: SilverStripe\Assets\Storage\GeneratedAssetHandler - SilverStripe\Filesystem\Storage\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\GeneratedAssetHandler - 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 - 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 - GDBackend: SilverStripe\Assets\GDBackend - Image: SilverStripe\Assets\Image - Image_Backend: SilverStripe\Assets\Image_Backend - ImagickBackend: SilverStripe\Assets\ImagickBackend - Upload: SilverStripe\Assets\Upload - Upload_Validator: SilverStripe\Assets\Upload_Validator SilverStripe\Framework\Core\Configurable: SilverStripe\Core\Config\Configurable PaginatedList: SilverStripe\ORM\PaginatedList ArrayLib: SilverStripe\ORM\ArrayLib @@ -523,23 +491,6 @@ mappings: ModelAdminTest_Contact: SilverStripe\Admin\Tests\ModelAdminTest\Contact ModelAdminTest_Player: SilverStripe\Admin\Tests\ModelAdminTest\Player SecurityAdminTest: SilverStripe\Admin\Tests\SecurityAdminTest - AssetControlExtensionTest: SilverStripe\Assets\Tests\AssetControlExtensionTest - AssetControlExtensionTest_VersionedObject: SilverStripe\Assets\Tests\VersionedObject - AssetControlExtensionTest_Object: SilverStripe\Assets\Tests\TestObject - AssetControlExtensionTest_ArchivedObject: SilverStripe\Assets\Tests\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 - FolderTest: SilverStripe\Assets\Tests\FolderTest - GDTest: SilverStripe\Assets\Tests\GDTest - GDBackend_ImageUnavailable: SilverStripe\Assets\Tests\GDTest\ImageUnavailable - ProtectedFileControllerTest: SilverStripe\Assets\Tests\ProtectedFileControllerTest - UploadTest: SilverStripe\Assets\Tests\UploadTest - UploadTest_Validator: SilverStripe\Assets\Tests\UploadTest\UploadTest_Validator ControllerTest: SilverStripe\Control\Tests\ControllerTest ControllerTest_Controller: SilverStripe\Control\Tests\ControllerTest\TestController ControllerTest_UnsecuredController: SilverStripe\Control\Tests\ControllerTest\UnsecuredController @@ -663,10 +614,6 @@ mappings: DateFieldViewJQueryTest: SilverStripe\Forms\Tests\DateFieldViewJQueryTest DatetimeFieldTest: SilverStripe\Forms\Tests\DatetimeFieldTest DatetimeFieldTest_Model: SilverStripe\Forms\Tests\DatetimeFieldTest\Model - DBFileTest: SilverStripe\Forms\Tests\DBFileTest - DBFileTest_Object: SilverStripe\Forms\Tests\DBFileTest\TestObject - DBFileTest_Subclass: SilverStripe\Forms\Tests\DBFileTest\Subclass - DBFileTest_ImageOnly: SilverStripe\Forms\Tests\DBFileTest\ImageOnly DropdownFieldTest: SilverStripe\Forms\Tests\DropdownFieldTest EmailFieldTest: SilverStripe\Forms\Tests\EmailFieldTest EmailFieldTest_Validator: SilverStripe\Forms\Tests\EmailFieldTest\TestValidator @@ -842,15 +789,12 @@ mappings: DBYearTest: SilverStripe\ORM\Tests\DBYearTest DecimalTest: SilverStripe\ORM\Tests\DecimalTest DecimalTest_DataObject: SilverStripe\ORM\Tests\DecimalTest\TestObject - GDImageTest: SilverStripe\ORM\Tests\GDImageTest GroupedListTest: SilverStripe\ORM\Tests\GroupedListTest HasManyListTest: SilverStripe\ORM\Tests\HasManyListTest HierarchyTest: SilverStripe\ORM\Tests\HierarchyTest HierarchyTest_Object: SilverStripe\ORM\Tests\HierarchyTest\TestObject HierarchyHideTest_Object: SilverStripe\ORM\Tests\HierarchyTest\HideTestObject HierarchyHideTest_SubObject: SilverStripe\ORM\Tests\HierarchyHideTest\HideTestSubObject - ImageTest: SilverStripe\ORM\Tests\ImageTest - ImagickImageTest: SilverStripe\ORM\Tests\ImagickImageTest LabelFieldTest: SilverStripe\ORM\Tests\LabelFieldTest ManyManyListExtensionTest: SilverStripe\ORM\Tests\ManyManyListExtensionTest ManyManyListTest_IndirectPrimary: SilverStripe\ORM\Tests\ManyManyListTest\IndirectPrimary @@ -971,7 +915,6 @@ mappings: ViewableDataTest_Cached: SilverStripe\View\Tests\ViewableDataTest\Cached ViewableDataTest_NotCached: SilverStripe\View\Tests\ViewableDataTest\NotCached ViewableDataTest_Failover: SilverStripe\View\Tests\ViewableDataTest\Failover - AssetStoreTest_SpyStore: SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore GridField_URLHandlerTest: SilverStripe\Forms\Tests\GridField\GridField_URLHandlerTest GridField_URLHandlerTest_Controller: SilverStripe\Forms\Tests\GridField\GridField_URLHandlerTest\TestController GridField_URLHandlerTest_Component: SilverStripe\Forms\Tests\GridField\GridField_URLHandlerTest\TestComponent @@ -1046,7 +989,6 @@ mappings: NamespacedClassManifestTest: SilverStripe\Core\Tests\Manifest\NamespacedClassManifestTest ThemeResourceLoaderTest: SilverStripe\Core\Tests\Manifest\ThemeResourceLoaderTest TokenisedRegularExpressionTest: SilverStripe\Core\Tests\Manifest\TokenisedRegularExpressionTest - AssetAdapterTest: SilverStripe\Assets\Tests\Flysystem\AssetAdapterTest EmailTest: SilverStripe\Control\Tests\Email\EmailTest EmailTest_Mailer: SilverStripe\Control\Tests\Email\EmailTest\TestMailer MailerTest: SilverStripe\Control\Tests\Email\MailerTest diff --git a/_config/asset.yml b/_config/asset.yml index 93c04adbd..c4c507ff0 100644 --- a/_config/asset.yml +++ b/_config/asset.yml @@ -1,72 +1,4 @@ --- -Name: coreflysystem ---- -SilverStripe\Core\Injector\Injector: - # Define the default adapter for this filesystem - FlysystemPublicAdapter: - class: 'SilverStripe\Assets\Flysystem\PublicAssetAdapter' - # Define the secondary adapter for protected assets - FlysystemProtectedAdapter: - class: 'SilverStripe\Assets\Flysystem\ProtectedAssetAdapter' - # Define the default filesystem - FlysystemPublicBackend: - class: 'League\Flysystem\Filesystem' - constructor: - Adapter: '%$FlysystemPublicAdapter' - Config: - visibility: public - # Define the secondary filesystem for protected assets - FlysystemProtectedBackend: - class: 'League\Flysystem\Filesystem' - constructor: - Adapter: '%$FlysystemProtectedAdapter' - Config: - visibility: private ---- -Name: coreassets -After: - - '#coreflysystem' ---- -SilverStripe\Core\Injector\Injector: - # Define our SS asset backend - AssetStore: - class: 'SilverStripe\Assets\Flysystem\FlysystemAssetStore' - properties: - PublicFilesystem: '%$FlysystemPublicBackend' - ProtectedFilesystem: '%$FlysystemProtectedBackend' - ProtectedFileController: - class: SilverStripe\Assets\Storage\ProtectedFileController - properties: - RouteHandler: '%$AssetStore' - AssetNameGenerator: - class: SilverStripe\Assets\Storage\DefaultAssetNameGenerator - type: prototype - # Requirements config - GeneratedAssetHandler: - class: SilverStripe\Assets\Flysystem\GeneratedAssetHandler - properties: - Filesystem: '%$FlysystemPublicBackend' - SilverStripe\View\Requirements_Minifier: - class: SilverStripe\View\JSMinifier - SilverStripe\View\Requirements_Backend: - properties: - AssetHandler: '%$GeneratedAssetHandler' ---- -Name: coreassetroutes -After: - - '#coreassets' ---- -SilverStripe\Control\Director: - rules: - 'assets': ProtectedFileController ---- -Name: imageconfig ---- -SilverStripe\Core\Injector\Injector: - SilverStripe\Assets\Image_Backend: - class: SilverStripe\Assets\GDBackend - Image_Backend: '%$SilverStripe\Assets\Image_Backend' ---- Name: coreassetfield --- SilverStripe\Core\Injector\Injector: diff --git a/_config/model.yml b/_config/model.yml index 983dee31e..452c768f6 100644 --- a/_config/model.yml +++ b/_config/model.yml @@ -20,8 +20,6 @@ SilverStripe\Core\Injector\Injector: class: SilverStripe\ORM\FieldType\DBDouble Enum: class: SilverStripe\ORM\FieldType\DBEnum - DBFile: - class: SilverStripe\Assets\Storage\DBFile Float: class: SilverStripe\ORM\FieldType\DBFloat ForeignKey: diff --git a/composer.json b/composer.json index 5d3782fe9..922651344 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "monolog/monolog": "~1.11", "nikic/php-parser": "^2 || ^3", "silverstripe/config": "^1@dev", + "silverstripe/assets": "^1@dev", "swiftmailer/swiftmailer": "~5.4", "symfony/cache": "^3.3@dev", "symfony/config": "^2.8", @@ -44,8 +45,6 @@ }, "autoload": { "psr-4": { - "SilverStripe\\Assets\\": "src/Assets/", - "SilverStripe\\Assets\\Tests\\": "tests/php/Assets/", "SilverStripe\\Control\\": "src/Control/", "SilverStripe\\Control\\Tests\\": "tests/php/Control/", "SilverStripe\\Core\\": "src/Core/", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index bbff0c2ec..5c9a0ec3a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -20,6 +20,7 @@ tests/php + silverstripe-assets/tests/php @@ -37,9 +38,7 @@ . thirdparty/ - admin/thirdparty/ tests/php/ - admin/tests/php/ diff --git a/src/Assets/AssetControlExtension.php b/src/Assets/AssetControlExtension.php deleted file mode 100644 index 48443a957..000000000 --- a/src/Assets/AssetControlExtension.php +++ /dev/null @@ -1,304 +0,0 @@ -addAssetsFromRecord($manipulations, $this->owner, AssetManipulationList::STATE_DELETED); - - // Whitelist assets that exist in other stages - $this->addAssetsFromOtherStages($manipulations); - - // Apply visibility rules based on the final manipulation - $this->processManipulation($manipulations); - } - - /** - * Ensure that changes to records flush overwritten files, and update the visibility - * of other assets. - */ - public function onBeforeWrite() - { - // Prepare blank manipulation - $manipulations = new AssetManipulationList(); - - // Mark overwritten object as deleted - if ($this->owner->isInDB()) { - $priorRecord = DataObject::get(get_class($this->owner))->byID($this->owner->ID); - if ($priorRecord) { - $this->addAssetsFromRecord($manipulations, $priorRecord, AssetManipulationList::STATE_DELETED); - } - } - - // Add assets from new record with the correct visibility rules - $state = $this->getRecordState($this->owner); - $this->addAssetsFromRecord($manipulations, $this->owner, $state); - - // Whitelist assets that exist in other stages - $this->addAssetsFromOtherStages($manipulations); - - // Apply visibility rules based on the final manipulation - $this->processManipulation($manipulations); - } - - /** - * Check default state of this record - * - * @param DataObject $record - * @return string One of AssetManipulationList::STATE_* constants - */ - protected function getRecordState($record) - { - if ($this->isVersioned()) { - // Check stage this record belongs to - $stage = $record->getSourceQueryParam('Versioned.stage') ?: Versioned::get_stage(); - - // Non-live stages are automatically non-public - if ($stage !== Versioned::LIVE) { - return AssetManipulationList::STATE_PROTECTED; - } - } - - // Check if canView permits anonymous viewers - return Member::actAs(null, function () use ($record) { - return $record->canView() - ? AssetManipulationList::STATE_PUBLIC - : AssetManipulationList::STATE_PROTECTED; - }); - } - - /** - * Given a set of asset manipulations, trigger any necessary publish, protect, or - * delete actions on each asset. - * - * @param AssetManipulationList $manipulations - */ - protected function processManipulation(AssetManipulationList $manipulations) - { - // When deleting from stage then check if we should archive assets - $archive = $this->owner->config()->get('keep_archived_assets'); - - // Publish assets - $this->publishAll($manipulations->getPublicAssets()); - - // Protect assets - $this->protectAll($manipulations->getProtectedAssets()); - - // Check deletion policy - $deletedAssets = $manipulations->getDeletedAssets(); - if ($archive && $this->isVersioned()) { - // Archived assets are kept protected - $this->protectAll($deletedAssets); - } else { - // Otherwise remove all assets - $this->deleteAll($deletedAssets); - } - } - - /** - * Checks all stages other than the current stage, and check the visibility - * of assets attached to those records. - * - * @param AssetManipulationList $manipulation Set of manipulations to add assets to - */ - protected function addAssetsFromOtherStages(AssetManipulationList $manipulation) - { - // Skip unversioned or unsaved assets - if (!$this->isVersioned() || !$this->owner->isInDB()) { - return; - } - - // Unauthenticated member to use for checking visibility - $baseClass = $this->owner->baseClass(); - $baseTable = $this->owner->baseTable(); - $filter = array("\"{$baseTable}\".\"ID\"" => $this->owner->ID); - $stages = $this->owner->getVersionedStages(); // {@see Versioned::getVersionedStages} - foreach ($stages as $stage) { - // Skip current stage; These should be handled explicitly - if ($stage === Versioned::get_stage()) { - continue; - } - - // Check if record exists in this stage - $record = Versioned::get_one_by_stage($baseClass, $stage, $filter); - if (!$record) { - continue; - } - - // Check visibility of this record, and record all attached assets - $state = $this->getRecordState($record); - $this->addAssetsFromRecord($manipulation, $record, $state); - } - } - - /** - * Given a record, add all assets it contains to the given manipulation. - * State can be declared for this record, otherwise the underlying DataObject - * will be queried for canView() to see if those assets are public - * - * @param AssetManipulationList $manipulation Set of manipulations to add assets to - * @param DataObject $record Record - * @param string $state One of AssetManipulationList::STATE_* constant values. - */ - protected function addAssetsFromRecord(AssetManipulationList $manipulation, DataObject $record, $state) - { - // Find all assets attached to this record - $assets = $this->findAssets($record); - if (empty($assets)) { - return; - } - - // Add all assets to this stage - foreach ($assets as $asset) { - $manipulation->addAsset($asset, $state); - } - } - - /** - * Return a list of all tuples attached to this dataobject - * Note: Variants are excluded - * - * @param DataObject $record to search - * @return array - */ - protected function findAssets(DataObject $record) - { - // Search for dbfile instances - $files = array(); - $fields = DataObject::getSchema()->fieldSpecs($record); - foreach ($fields as $field => $db) { - $fieldObj = $record->$field; - if (!($fieldObj instanceof DBFile)) { - continue; - } - - // Omit variant and merge with set - $next = $record->dbObject($field)->getValue(); - unset($next['Variant']); - if ($next) { - $files[] = $next; - } - } - - // De-dupe - return array_map("unserialize", array_unique(array_map("serialize", $files))); - } - - /** - * Determine if {@see Versioned) extension rules should be applied to this object - * - * @return bool - */ - protected function isVersioned() - { - return $this->owner->has_extension('SilverStripe\\ORM\\Versioning\\Versioned') && class_exists('SilverStripe\\ORM\\Versioning\\Versioned'); - } - - /** - * Delete all assets in the tuple list - * - * @param array $assets - */ - protected function deleteAll($assets) - { - if (empty($assets)) { - return; - } - $store = $this->getAssetStore(); - foreach ($assets as $asset) { - $store->delete($asset['Filename'], $asset['Hash']); - } - } - - /** - * Move all assets in the list to the public store - * - * @param array $assets - */ - protected function publishAll($assets) - { - if (empty($assets)) { - return; - } - - $store = $this->getAssetStore(); - foreach ($assets as $asset) { - $store->publish($asset['Filename'], $asset['Hash']); - } - } - - /** - * Move all assets in the list to the protected store - * - * @param array $assets - */ - protected function protectAll($assets) - { - if (empty($assets)) { - return; - } - $store = $this->getAssetStore(); - foreach ($assets as $asset) { - $store->protect($asset['Filename'], $asset['Hash']); - } - } - - /** - * @return AssetStore - */ - protected function getAssetStore() - { - return Injector::inst()->get('AssetStore'); - } -} diff --git a/src/Assets/AssetManipulationList.php b/src/Assets/AssetManipulationList.php deleted file mode 100644 index e6a74e82d..000000000 --- a/src/Assets/AssetManipulationList.php +++ /dev/null @@ -1,175 +0,0 @@ -addPublicAsset($asset); - case self::STATE_PROTECTED: - return $this->addProtectedAsset($asset); - case self::STATE_DELETED: - return $this->addDeletedAsset($asset); - default: - throw new \InvalidArgumentException("Invalid state {$state}"); - } - } - - /** - * Mark a file as public - * - * @param array $asset Asset tuple - * @return bool True if the asset was added to the public set - */ - public function addPublicAsset($asset) - { - // Remove from protected / deleted lists - $key = $this->getAssetKey($asset); - unset($this->protected[$key]); - unset($this->deleted[$key]); - // Skip if already public - if (isset($this->public[$key])) { - return false; - } - unset($asset['Variant']); - $this->public[$key] = $asset; - return true; - } - - /** - * Record an asset as protected - * - * @param array $asset Asset tuple - * @return bool True if the asset was added to the protected set - */ - public function addProtectedAsset($asset) - { - $key = $this->getAssetKey($asset); - // Don't demote from public - if (isset($this->public[$key])) { - return false; - } - unset($this->deleted[$key]); - // Skip if already protected - if (isset($this->protected[$key])) { - return false; - } - unset($asset['Variant']); - $this->protected[$key] = $asset; - return true; - } - - /** - * Record an asset as deleted - * - * @param array $asset Asset tuple - * @return bool True if the asset was added to the deleted set - */ - public function addDeletedAsset($asset) - { - $key = $this->getAssetKey($asset); - // Only delete if this doesn't exist in any non-deleted state - if (isset($this->public[$key]) || isset($this->protected[$key])) { - return false; - } - // Skip if already deleted - if (isset($this->deleted[$key])) { - return false; - } - unset($asset['Variant']); - $this->deleted[$key] = $asset; - return true; - } - - /** - * Get all public assets - * - * @return array - */ - public function getPublicAssets() - { - return $this->public; - } - - /** - * Get protected assets - * - * @return array - */ - public function getProtectedAssets() - { - return $this->protected; - } - - /** - * Get deleted assets - * - * @return array - */ - public function getDeletedAssets() - { - return $this->deleted; - } -} diff --git a/src/Assets/File.php b/src/Assets/File.php deleted file mode 100644 index c48db1501..000000000 --- a/src/Assets/File.php +++ /dev/null @@ -1,1320 +0,0 @@ -Security - * - * Caution: It is recommended to disable any script execution in the "assets/" - * directory in the webserver configuration, to reduce the risk of exploits. - * See http://doc.silverstripe.org/secure-development#filesystem - * - * Asset storage - * - * As asset storage is configured separately to any File DataObject records, this class - * does not make any assumptions about how these records are saved. They could be on - * a local filesystem, remote filesystem, or a virtual record container (such as in local memory). - * - * The File dataobject simply represents an externally facing view of shared resources - * within this asset store. - * - * Internally individual files are referenced by a"Filename" parameter, which represents a File, extension, - * and is optionally prefixed by a list of custom directories. This path is root-agnostic, so it does not - * automatically have a direct url mapping (even to the site's base directory). - * - * Additionally, individual files may have several versions distinguished by sha1 hash, - * of which a File DataObject can point to a single one. Files can also be distinguished by - * variants, which may be resized images or format-shifted documents. - * - * Properties - * - * - "Title": Optional title of the file (for display purposes only). - * Defaults to "Name". Note that the Title field of Folder (subclass of File) - * is linked to Name, so Name and Title will always be the same. - * -"File": Physical asset backing this DB record. This is a composite DB field with - * its own list of properties. {@see DBFile} for more information - * - "Content": Typically unused, but handy for a textual representation of - * files, e.g. for fulltext indexing of PDF documents. - * - "ParentID": Points to a {@link Folder} record. Should be in sync with - * "Filename". A ParentID=0 value points to the "assets/" folder, not the webroot. - * -"ShowInSearch": True if this file is searchable - * - * @property string $Name Basename of the file - * @property string $Title Title of the file - * @property DBFile $File asset stored behind this File record - * @property string $Content - * @property string $ShowInSearch Boolean that indicates if file is shown in search. Doesn't apply to Folders - * @property int $ParentID ID of parent File/Folder - * @property int $OwnerID ID of Member who owns the file - * - * @method File Parent() Returns parent File - * @method Member Owner() Returns Member object of file owner. - * - * @mixin Hierarchy - * @mixin Versioned - */ -class File extends DataObject implements ShortcodeHandler, AssetContainer, Thumbnail, CMSPreviewable -{ - - use ImageManipulation; - - private static $default_sort = "\"Name\""; - - /** - * @config - * @var string - */ - private static $singular_name = "File"; - - private static $plural_name = "Files"; - - /** - * Permissions necessary to view files outside of the live stage (e.g. archive / draft stage). - * - * @config - * @var array - */ - private static $non_live_permissions = array('CMS_ACCESS_LeftAndMain', 'CMS_ACCESS_AssetAdmin', 'VIEW_DRAFT_CONTENT'); - - private static $db = array( - "Name" => "Varchar(255)", - "Title" => "Varchar(255)", - "File" => "DBFile", - // Only applies to files, doesn't inherit for folder - 'ShowInSearch' => 'Boolean(1)', - ); - - private static $has_one = array( - "Parent" => "SilverStripe\\Assets\\File", - "Owner" => "SilverStripe\\Security\\Member" - ); - - private static $defaults = array( - "ShowInSearch" => 1, - ); - - private static $extensions = array( - "SilverStripe\\ORM\\Hierarchy\\Hierarchy", - "SilverStripe\\ORM\\Versioning\\Versioned" - ); - - private static $casting = array ( - 'TreeTitle' => 'HTMLFragment' - ); - - private static $table_name = 'File'; - - /** - * @config - * @var array List of allowed file extensions, enforced through {@link validate()}. - * - * Note: if you modify this, you should also change a configuration file in the assets directory. - * Otherwise, the files will be able to be uploaded but they won't be able to be served by the - * webserver. - * - * - If you are running Apache you will need to change assets/.htaccess - * - If you are running IIS you will need to change assets/web.config - * - * Instructions for the change you need to make are included in a comment in the config file. - */ - private static $allowed_extensions = array( - '', 'ace', 'arc', 'arj', 'asf', 'au', 'avi', 'bmp', 'bz2', 'cab', 'cda', 'css', 'csv', 'dmg', 'doc', - 'docx', 'dotx', 'dotm', 'flv', 'gif', 'gpx', 'gz', 'hqx', 'ico', 'jar', 'jpeg', 'jpg', 'js', 'kml', - 'm4a', 'm4v', 'mid', 'midi', 'mkv', 'mov', 'mp3', 'mp4', 'mpa', 'mpeg', 'mpg', 'ogg', 'ogv', 'pages', - 'pcx', 'pdf', 'png', 'pps', 'ppt', 'pptx', 'potx', 'potm', 'ra', 'ram', 'rm', 'rtf', 'sit', 'sitx', - 'tar', 'tgz', 'tif', 'tiff', 'txt', 'wav', 'webm', 'wma', 'wmv', 'xls', 'xlsx', 'xltx', 'xltm', 'zip', - 'zipx', - ); - - /** - * @config - * @var array Category identifiers mapped to commonly used extensions. - */ - private static $app_categories = array( - 'archive' => array( - 'ace', 'arc', 'arj', 'bz', 'bz2', 'cab', 'dmg', 'gz', 'hqx', 'jar', 'rar', 'sit', 'sitx', 'tar', 'tgz', - 'zip', 'zipx', - ), - 'audio' => array( - 'aif', 'aifc', 'aiff', 'apl', 'au', 'avr', 'cda', 'm4a', 'mid', 'midi', 'mp3', 'ogg', 'ra', - 'ram', 'rm', 'snd', 'wav', 'wma', - ), - 'document' => array( - 'css', 'csv', 'doc', 'docx', 'dotm', 'dotx', 'htm', 'html', 'gpx', 'js', 'kml', 'pages', 'pdf', - 'potm', 'potx', 'pps', 'ppt', 'pptx', 'rtf', 'txt', 'xhtml', 'xls', 'xlsx', 'xltm', 'xltx', 'xml', - ), - 'image' => array( - 'alpha', 'als', 'bmp', 'cel', 'gif', 'ico', 'icon', 'jpeg', 'jpg', 'pcx', 'png', 'ps', 'tif', 'tiff', - ), - 'image/supported' => array( - 'gif', 'jpeg', 'jpg', 'png' - ), - 'flash' => array( - 'fla', 'swf' - ), - 'video' => array( - 'asf', 'avi', 'flv', 'ifo', 'm1v', 'm2v', 'm4v', 'mkv', 'mov', 'mp2', 'mp4', 'mpa', 'mpe', 'mpeg', - 'mpg', 'ogv', 'qt', 'vob', 'webm', 'wmv', - ), - ); - - /** - * Map of file extensions to class type - * - * @config - * @var - */ - private static $class_for_file_extension = array( - '*' => 'SilverStripe\\Assets\\File', - 'jpg' => 'SilverStripe\\Assets\\Image', - 'jpeg' => 'SilverStripe\\Assets\\Image', - 'png' => 'SilverStripe\\Assets\\Image', - 'gif' => 'SilverStripe\\Assets\\Image', - ); - - /** - * @config - * @var bool If this is true, then restrictions set in {@link $allowed_max_file_size} and - * {@link $allowed_extensions} will be applied to users with admin privileges as - * well. - */ - private static $apply_restrictions_to_admin = true; - - /** - * If enabled, legacy file dataobjects will be automatically imported into the APL - * - * @config - * @var bool - */ - private static $migrate_legacy_file = false; - - /** - * @config - * @var boolean - */ - private static $update_filesystem = true; - - public static function get_shortcodes() - { - return 'file_link'; - } - - /** - * Replace "[file_link id=n]" shortcode with an anchor tag or link to the file. - * - * @param array $arguments Arguments passed to the parser - * @param string $content Raw shortcode - * @param ShortcodeParser $parser Parser - * @param string $shortcode Name of shortcode used to register this handler - * @param array $extra Extra arguments - * @return string Result of the handled shortcode - */ - public static function handle_shortcode($arguments, $content, $parser, $shortcode, $extra = array()) - { - // Find appropriate record, with fallback for error handlers - $record = static::find_shortcode_record($arguments, $errorCode); - if ($errorCode) { - $record = static::find_error_record($errorCode); - } - if (!$record) { - return null; // There were no suitable matches at all. - } - - // build the HTML tag - if ($content) { - // build some useful meta-data (file type and size) as data attributes - $attrs = ' '; - if ($record instanceof File) { - foreach (array( - 'class' => 'file', - 'data-type' => $record->getExtension(), - 'data-size' => $record->getSize() - ) as $name => $value) { - $attrs .= sprintf('%s="%s" ', $name, $value); - } - } - - return sprintf('%s', $record->Link(), rtrim($attrs), $parser->parse($content)); - } else { - return $record->Link(); - } - } - - /** - * Find the record to use for a given shortcode. - * - * @param array $args Array of input shortcode arguments - * @param int $errorCode If the file is not found, or is inaccessible, this will be assigned to a HTTP error code. - * @return File|null The File DataObject, if it can be found. - */ - public static function find_shortcode_record($args, &$errorCode = null) - { - // Validate shortcode - if (!isset($args['id']) || !is_numeric($args['id'])) { - return null; - } - - // Check if the file is found - /** @var File $file */ - $file = File::get()->byID($args['id']); - if (!$file) { - $errorCode = 404; - return null; - } - - // Check if the file is viewable - if (!$file->canView()) { - $errorCode = 403; - return null; - } - - // Success - return $file; - } - - /** - * Given a HTTP Error, find an appropriate substitute File or SiteTree data object instance. - * - * @param int $errorCode HTTP Error value - * @return File|SiteTree File or SiteTree object to use for the given error - */ - protected static function find_error_record($errorCode) - { - $result = static::singleton()->invokeWithExtensions('getErrorRecordFor', $errorCode); - $result = array_filter($result); - if ($result) { - return reset($result); - } - return null; - } - - /** - * A file only exists if the file_exists() and is in the DB as a record - * - * Use $file->isInDB() to only check for a DB record - * Use $file->File->exists() to only check if the asset exists - * - * @return bool - */ - public function exists() - { - return parent::exists() && $this->File->exists(); - } - - /** - * Find a File object by the given filename. - * - * @param string $filename Filename to search for, including any custom parent directories. - * @return File - */ - public static function find($filename) - { - // Split to folders and the actual filename, and traverse the structure. - $parts = explode("/", $filename); - $parentID = 0; - /** @var File $item */ - $item = null; - foreach ($parts as $part) { - $item = File::get()->filter(array( - 'Name' => $part, - 'ParentID' => $parentID - ))->first(); - if (!$item) { - break; - } - $parentID = $item->ID; - } - - return $item; - } - - /** - * Just an alias function to keep a consistent API with SiteTree - * - * @return string The link to the file - */ - public function Link() - { - return $this->getURL(); - } - - /** - * @deprecated 4.0 - */ - public function RelativeLink() - { - Deprecation::notice('4.0', 'Use getURL instead, as not all files will be relative to the site root.'); - return Director::makeRelative($this->getURL()); - } - - /** - * Just an alias function to keep a consistent API with SiteTree - * - * @return string The absolute link to the file - */ - public function AbsoluteLink() - { - return $this->getAbsoluteURL(); - } - - /** - * @return string - */ - public function getTreeTitle() - { - return Convert::raw2xml($this->Title); - } - - /** - * @param Member $member - * @return bool - */ - public function canView($member = null) - { - if (!$member) { - $member = Member::currentUser(); - } - - $result = $this->extendedCan('canView', $member); - if ($result !== null) { - return $result; - } - - return true; - } - - /** - * Check if this file can be modified - * - * @param Member $member - * @return boolean - */ - public function canEdit($member = null) - { - if (!$member) { - $member = Member::currentUser(); - } - - $result = $this->extendedCan('canEdit', $member); - if ($result !== null) { - return $result; - } - - return Permission::checkMember($member, array('CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain')); - } - - /** - * Check if a file can be created - * - * @param Member $member - * @param array $context - * @return boolean - */ - public function canCreate($member = null, $context = array()) - { - if (!$member) { - $member = Member::currentUser(); - } - - $result = $this->extendedCan('canCreate', $member, $context); - if ($result !== null) { - return $result; - } - - return $this->canEdit($member); - } - - /** - * Check if this file can be deleted - * - * @param Member $member - * @return boolean - */ - public function canDelete($member = null) - { - if (!$member) { - $member = Member::currentUser(); - } - - $result = $this->extendedCan('canDelete', $member); - if ($result !== null) { - return $result; - } - - return $this->canEdit($member); - } - - /** - * Returns the fields to power the edit screen of files in the CMS. - * You can modify this FieldList by subclassing folder, or by creating a {@link DataExtension} - * and implementing updateCMSFields(FieldList $fields) on that extension. - * - * @return FieldList - */ - public function getCMSFields() - { - $path = '/' . dirname($this->getFilename()); - - $previewLink = Convert::raw2att($this->PreviewLink()); - $image = ""; - - $statusTitle = $this->getStatusTitle(); - $statusFlag = ($statusTitle) ? "{$statusTitle}" : ''; - - $content = Tab::create( - 'Main', - HeaderField::create('TitleHeader', $this->Title, 1) - ->addExtraClass('editor__heading'), - LiteralField::create('StatusFlag', $statusFlag), - LiteralField::create("IconFull", $image) - ->addExtraClass('editor__file-preview'), - TabSet::create( - 'Editor', - Tab::create( - 'Details', - TextField::create("Title", $this->fieldLabel('Title')), - TextField::create("Name", $this->fieldLabel('Filename')), - ReadonlyField::create( - "Path", - _t('AssetTableField.PATH', 'Path'), - (($path !== '/.') ? $path : '') . '/' - ) - ), - Tab::create( - 'Usage', - DatetimeField::create( - "Created", - _t('AssetTableField.CREATED', 'First uploaded') - )->setReadonly(true), - DatetimeField::create( - "LastEdited", - _t('AssetTableField.LASTEDIT', 'Last changed') - )->setReadonly(true) - ) - ), - HiddenField::create('ID', $this->ID) - ); - - $fields = FieldList::create(TabSet::create('Root', $content)); - - $this->extend('updateCMSFields', $fields); - - return $fields; - } - - /** - * Get title for current file status - * - * @return string - */ - public function getStatusTitle() - { - $statusTitle = ''; - if ($this->isOnDraftOnly()) { - $statusTitle = _t('File.DRAFT', 'Draft'); - } elseif ($this->isModifiedOnDraft()) { - $statusTitle = _t('File.MODIFIED', 'Modified'); - } - return $statusTitle; - } - - /** - * Returns a category based on the file extension. - * This can be useful when grouping files by type, - * showing icons on filelinks, etc. - * Possible group values are: "audio", "mov", "zip", "image". - * - * @param string $ext Extension to check - * @return string - */ - public static function get_app_category($ext) - { - $ext = strtolower($ext); - foreach (static::config()->app_categories as $category => $exts) { - if (in_array($ext, $exts)) { - return $category; - } - } - return false; - } - - /** - * For a category or list of categories, get the list of file extensions - * - * @param array|string $categories List of categories, or single category - * @return array - */ - public static function get_category_extensions($categories) - { - if (empty($categories)) { - return array(); - } - - // Fix arguments into a single array - if (!is_array($categories)) { - $categories = array($categories); - } elseif (count($categories) === 1 && is_array(reset($categories))) { - $categories = reset($categories); - } - - // Check configured categories - $appCategories = self::config()->app_categories; - - // Merge all categories into list of extensions - $extensions = array(); - foreach (array_filter($categories) as $category) { - if (isset($appCategories[$category])) { - $extensions = array_merge($extensions, $appCategories[$category]); - } else { - throw new InvalidArgumentException("Unknown file category: $category"); - } - } - $extensions = array_unique($extensions); - sort($extensions); - return $extensions; - } - - /** - * Returns a category based on the file extension. - * - * @return string - */ - public function appCategory() - { - return self::get_app_category($this->getExtension()); - } - - - /** - * Should be called after the file was uploaded - */ - public function onAfterUpload() - { - $this->extend('onAfterUpload'); - } - - /** - * Make sure the file has a name - */ - protected function onBeforeWrite() - { - // Set default owner - if (!$this->isInDB() && !$this->OwnerID) { - $this->OwnerID = Member::currentUserID(); - } - - $name = $this->getField('Name'); - $title = $this->getField('Title'); - - $changed = $this->isChanged('Name'); - - // Name can't be blank, default to Title - if (!$name) { - $changed = true; - $name = $title; - } - - $filter = FileNameFilter::create(); - if ($name) { - // Fix illegal characters - $name = $filter->filter($name); - } else { - // Default to file name - $changed = true; - $name = $this->i18n_singular_name(); - $name = $filter->filter($name); - } - - // Check for duplicates when the name has changed (or is set for the first time) - if ($changed) { - $nameGenerator = $this->getNameGenerator($name); - // Defaults to returning the original filename on first iteration - foreach ($nameGenerator as $newName) { - // This logic is also used in the Folder subclass, but we're querying - // for duplicates on the File base class here (including the Folder subclass). - - // TODO Add read lock to avoid other processes creating files with the same name - // before this process has a chance to persist in the database. - $existingFile = File::get()->filter(array( - 'Name' => $newName, - 'ParentID' => (int) $this->ParentID - ))->exclude(array( - 'ID' => $this->ID - ))->first(); - if (!$existingFile) { - $name = $newName; - break; - } - } - } - - // Update actual field value - $this->setField('Name', $name); - - // Update title - if (!$title) { - // Generate a readable title, dashes and underscores replaced by whitespace, - // and any file extensions removed. - $this->setField( - 'Title', - str_replace(array('-','_'), ' ', preg_replace('/\.[^.]+$/', '', $name)) - ); - } - - // Propagate changes to the AssetStore and update the DBFile field - $this->updateFilesystem(); - - parent::onBeforeWrite(); - } - - /** - * This will check if the parent record and/or name do not match the name on the underlying - * DBFile record, and if so, copy this file to the new location, and update the record to - * point to this new file. - * - * This method will update the File {@see DBFile} field value on success, so it must be called - * before writing to the database - * - * @return bool True if changed - */ - public function updateFilesystem() - { - if (!$this->config()->update_filesystem) { - return false; - } - - // Check the file exists - if (!$this->File->exists()) { - return false; - } - - // Avoid moving files on live; Rely on this being done on stage prior to publish. - if (Versioned::get_stage() !== Versioned::DRAFT) { - return false; - } - - // Check path updated record will point to - // If no changes necessary, skip - $pathBefore = $this->File->getFilename(); - $pathAfter = $this->generateFilename(); - if ($pathAfter === $pathBefore) { - return false; - } - - // Copy record to new location via stream - $stream = $this->File->getStream(); - $this->File->setFromStream($stream, $pathAfter); - return true; - } - - /** - * Collate selected descendants of this page. - * $condition will be evaluated on each descendant, and if it is succeeds, that item will be added - * to the $collator array. - * - * @param string $condition The PHP condition to be evaluated. The page will be called $item - * @param array $collator An array, passed by reference, to collect all of the matching descendants. - * @return true|null - */ - public function collateDescendants($condition, &$collator) - { - if ($children = $this->Children()) { - foreach ($children as $item) { - /** @var File $item */ - if (!$condition || eval("return $condition;")) { - $collator[] = $item; - } - $item->collateDescendants($condition, $collator); - } - return true; - } - return null; - } - - /** - * Get an asset renamer for the given filename. - * - * @param string $filename Path name - * @return AssetNameGenerator - */ - protected function getNameGenerator($filename) - { - return Injector::inst()->createWithArgs('AssetNameGenerator', array($filename)); - } - - /** - * Gets the URL of this file - * - * @return string - */ - public function getAbsoluteURL() - { - $url = $this->getURL(); - if ($url) { - return Director::absoluteURL($url); - } - return null; - } - - /** - * Gets the URL of this file - * - * @uses Director::baseURL() - * @param bool $grant Ensures that the url for any protected assets is granted for the current user. - * @return string - */ - public function getURL($grant = true) - { - if ($this->File->exists()) { - return $this->File->getURL($grant); - } - return null; - } - - /** - * Get URL, but without resampling. - * - * @param bool $grant Ensures that the url for any protected assets is granted for the current user. - * @return string - */ - public function getSourceURL($grant = true) - { - if ($this->File->exists()) { - return $this->File->getSourceURL($grant); - } - return null; - } - - /** - * Get expected value of Filename tuple value. Will be used to trigger - * a file move on draft stage. - * - * @return string - */ - public function generateFilename() - { - // Check if this file is nested within a folder - $parent = $this->Parent(); - if ($parent && $parent->exists()) { - return $this->join_paths($parent->getFilename(), $this->Name); - } - return $this->Name; - } - - /** - * Ensure that parent folders are published before this one is published - * - * @todo Solve this via triggered publishing / ownership in the future - */ - public function onBeforePublish() - { - // Publish all parents from the root up - /** @var Folder $parent */ - foreach ($this->getAncestors()->reverse() as $parent) { - $parent->publishSingle(); - } - } - - /** - * Update the ParentID and Name for the given filename. - * - * On save, the underlying DBFile record will move the underlying file to this location. - * Thus it will not update the underlying Filename value until this is done. - * - * @param string $filename - * @return $this - */ - public function setFilename($filename) - { - // Check existing folder path - $folder = ''; - $parent = $this->Parent(); - if ($parent && $parent->exists()) { - $folder = $parent->Filename; - } - - // Detect change in foldername - $newFolder = ltrim(dirname(trim($filename, '/')), '.'); - if ($folder !== $newFolder) { - if (!$newFolder) { - $this->ParentID = 0; - } else { - $parent = Folder::find_or_make($newFolder); - $this->ParentID = $parent->ID; - } - } - - // Update base name - $this->Name = basename($filename); - return $this; - } - - /** - * Returns the file extension - * - * @return string - */ - public function getExtension() - { - return self::get_file_extension($this->Name); - } - - /** - * Gets the extension of a filepath or filename, - * by stripping away everything before the last "dot". - * Caution: Only returns the last extension in "double-barrelled" - * extensions (e.g. "gz" for "tar.gz"). - * - * Examples: - * - "myfile" returns "" - * - "myfile.txt" returns "txt" - * - "myfile.tar.gz" returns "gz" - * - * @param string $filename - * @return string - */ - public static function get_file_extension($filename) - { - return pathinfo($filename, PATHINFO_EXTENSION); - } - - /** - * Given an extension, determine the icon that should be used - * - * @param string $extension - * @return string Icon filename relative to base url - */ - public static function get_icon_for_extension($extension) - { - $extension = strtolower($extension); - - // Check if exact extension has an icon - if (!file_exists(FRAMEWORK_PATH ."/client/images/app_icons/{$extension}_92.png")) { - $extension = static::get_app_category($extension); - - // Fallback to category specific icon - if (!file_exists(FRAMEWORK_PATH ."/client/images/app_icons/{$extension}_92.png")) { - $extension ="generic"; - } - } - - return FRAMEWORK_DIR ."/client/images/app_icons/{$extension}_92.png"; - } - - /** - * Return the type of file for the given extension - * on the current file name. - * - * @return string - */ - public function getFileType() - { - return self::get_file_type($this->getFilename()); - } - - /** - * Get descriptive type of file based on filename - * - * @param string $filename - * @return string Description of file - */ - public static function get_file_type($filename) - { - $types = array( - 'gif' => _t('File.GifType', 'GIF image - good for diagrams'), - 'jpg' => _t('File.JpgType', 'JPEG image - good for photos'), - 'jpeg' => _t('File.JpgType', 'JPEG image - good for photos'), - 'png' => _t('File.PngType', 'PNG image - good general-purpose format'), - 'ico' => _t('File.IcoType', 'Icon image'), - 'tiff' => _t('File.TiffType', 'Tagged image format'), - 'doc' => _t('File.DocType', 'Word document'), - 'xls' => _t('File.XlsType', 'Excel spreadsheet'), - 'zip' => _t('File.ZipType', 'ZIP compressed file'), - 'gz' => _t('File.GzType', 'GZIP compressed file'), - 'dmg' => _t('File.DmgType', 'Apple disk image'), - 'pdf' => _t('File.PdfType', 'Adobe Acrobat PDF file'), - 'mp3' => _t('File.Mp3Type', 'MP3 audio file'), - 'wav' => _t('File.WavType', 'WAV audo file'), - 'avi' => _t('File.AviType', 'AVI video file'), - 'mpg' => _t('File.MpgType', 'MPEG video file'), - 'mpeg' => _t('File.MpgType', 'MPEG video file'), - 'js' => _t('File.JsType', 'Javascript file'), - 'css' => _t('File.CssType', 'CSS file'), - 'html' => _t('File.HtmlType', 'HTML file'), - 'htm' => _t('File.HtmlType', 'HTML file') - ); - - // Get extension - $extension = strtolower(self::get_file_extension($filename)); - return isset($types[$extension]) ? $types[$extension] : 'unknown'; - } - - /** - * Returns the size of the file type in an appropriate format. - * - * @return string|false String value, or false if doesn't exist - */ - public function getSize() - { - $size = $this->getAbsoluteSize(); - if ($size) { - return static::format_size($size); - } - return false; - } - - /** - * Formats a file size (eg: (int)42 becomes string '42 bytes') - * - * @param int $size - * @return string - */ - public static function format_size($size) - { - if ($size < 1024) { - return $size . ' bytes'; - } - if ($size < 1024*10) { - return (round($size/1024*10)/10). ' KB'; - } - if ($size < 1024*1024) { - return round($size/1024) . ' KB'; - } - if ($size < 1024*1024*10) { - return (round(($size/1024)/1024*10)/10) . ' MB'; - } - if ($size < 1024*1024*1024) { - return round(($size/1024)/1024) . ' MB'; - } - return round($size/(1024*1024*1024)*10)/10 . ' GB'; - } - - /** - * Convert a php.ini value (eg: 512M) to bytes - * - * @param string $iniValue - * @return int - */ - public static function ini2bytes($iniValue) - { - $iniValues = str_split(trim($iniValue)); - $unit = strtolower(array_pop($iniValues)); - $quantity = (int) implode($iniValues); - switch ($unit) { - case 'g': - $quantity *= 1024; - // deliberate no break - case 'm': - $quantity *= 1024; - // deliberate no break - case 'k': - $quantity *= 1024; - // deliberate no break - default: - // no-op: pre-existing behaviour - break; - } - return $quantity; - } - - /** - * Return file size in bytes. - * - * @return int - */ - public function getAbsoluteSize() - { - return $this->File->getAbsoluteSize(); - } - - public function validate() - { - $result = ValidationResult::create(); - $this->File->validate($result, $this->Name); - $this->extend('validate', $result); - return $result; - } - - /** - * Maps a {@link File} subclass to a specific extension. - * By default, files with common image extensions will be created - * as {@link Image} instead of {@link File} when using - * {@link Folder::constructChild}, {@link Folder::addUploadToFolder}), - * and the {@link Upload} class (either directly or through {@link FileField}). - * For manually instanciated files please use this mapping getter. - * - * Caution: Changes to mapping doesn't apply to existing file records in the database. - * Also doesn't hook into {@link Object::getCustomClass()}. - * - * @param String File extension, without dot prefix. Use an asterisk ('*') - * to specify a generic fallback if no mapping is found for an extension. - * @return String Classname for a subclass of {@link File} - */ - public static function get_class_for_file_extension($ext) - { - $map = array_change_key_case(self::config()->class_for_file_extension, CASE_LOWER); - return (array_key_exists(strtolower($ext), $map)) ? $map[strtolower($ext)] : $map['*']; - } - - /** - * See {@link get_class_for_file_extension()}. - * - * @param String|array - * @param String - */ - public static function set_class_for_file_extension($exts, $class) - { - if (!is_array($exts)) { - $exts = array($exts); - } - foreach ($exts as $ext) { - if (!is_subclass_of($class, 'SilverStripe\\Assets\\File')) { - throw new InvalidArgumentException( - sprintf('Class "%s" (for extension "%s") is not a valid subclass of File', $class, $ext) - ); - } - self::config()->class_for_file_extension = array($ext => $class); - } - } - - public function getMetaData() - { - if (!$this->File->exists()) { - return null; - } - return $this->File->getMetaData(); - } - - public function getMimeType() - { - if (!$this->File->exists()) { - return null; - } - return $this->File->getMimeType(); - } - - public function getStream() - { - if (!$this->File->exists()) { - return null; - } - return $this->File->getStream(); - } - - public function getString() - { - if (!$this->File->exists()) { - return null; - } - return $this->File->getString(); - } - - public function setFromLocalFile($path, $filename = null, $hash = null, $variant = null, $config = array()) - { - $result = $this->File->setFromLocalFile($path, $filename, $hash, $variant, $config); - - // Update File record to name of the uploaded asset - if ($result) { - $this->setFilename($result['Filename']); - } - return $result; - } - - public function setFromStream($stream, $filename, $hash = null, $variant = null, $config = array()) - { - $result = $this->File->setFromStream($stream, $filename, $hash, $variant, $config); - - // Update File record to name of the uploaded asset - if ($result) { - $this->setFilename($result['Filename']); - } - return $result; - } - - public function setFromString($data, $filename, $hash = null, $variant = null, $config = array()) - { - $result = $this->File->setFromString($data, $filename, $hash, $variant, $config); - - // Update File record to name of the uploaded asset - if ($result) { - $this->setFilename($result['Filename']); - } - return $result; - } - - public function getIsImage() - { - return false; - } - - public function getFilename() - { - return $this->File->Filename; - } - - public function getHash() - { - return $this->File->Hash; - } - - public function getVariant() - { - return $this->File->Variant; - } - - /** - * Return a html5 tag of the appropriate for this file (normally img or a) - * - * @return string - */ - public function forTemplate() - { - return $this->getTag() ?: ''; - } - - /** - * Return a html5 tag of the appropriate for this file (normally img or a) - * - * @return string - */ - public function getTag() - { - $template = $this->File->getFrontendTemplate(); - if (empty($template)) { - return ''; - } - return (string)$this->renderWith($template); - } - - public function requireDefaultRecords() - { - parent::requireDefaultRecords(); - - // Check if old file records should be migrated - if (!$this->config()->migrate_legacy_file) { - return; - } - - $migrated = FileMigrationHelper::singleton()->run(); - if ($migrated) { - DB::alteration_message("{$migrated} File DataObjects upgraded", "changed"); - } - } - - /** - * Joins one or more segments together to build a Filename identifier. - * - * Note that the result will not have a leading slash, and should not be used - * with local file paths. - * - * @param string $part,... Parts - * @return string - */ - public static function join_paths($part = null) - { - $args = func_get_args(); - if (count($args) === 1 && is_array($args[0])) { - $args = $args[0]; - } - - $parts = array(); - foreach ($args as $arg) { - $part = trim($arg, ' \\/'); - if ($part) { - $parts[] = $part; - } - } - - return implode('/', $parts); - } - - public function deleteFile() - { - return $this->File->deleteFile(); - } - - public function getVisibility() - { - return $this->File->getVisibility(); - } - - public function publishFile() - { - $this->File->publishFile(); - } - - public function protectFile() - { - $this->File->protectFile(); - } - - public function grantFile() - { - $this->File->grantFile(); - } - - public function revokeFile() - { - $this->File->revokeFile(); - } - - public function canViewFile() - { - return $this->File->canViewFile(); - } - - public function CMSEditLink() - { - $link = null; - $this->extend('updateCMSEditLink', $link); - return $link; - } - - public function PreviewLink($action = null) - { - // Since AbsoluteURL can whitelist protected assets, - // do permission check first - if (!$this->canView()) { - return null; - } - $link = $this->getIcon(); - $this->extend('updatePreviewLink', $link, $action); - return $link; - } -} diff --git a/src/Assets/FileFinder.php b/src/Assets/FileFinder.php deleted file mode 100644 index 64f1630be..000000000 --- a/src/Assets/FileFinder.php +++ /dev/null @@ -1,278 +0,0 @@ - null, - 'dir_regex' => null, - 'accept_callback' => null, - 'accept_dir_callback' => null, - 'accept_file_callback' => null, - 'file_callback' => null, - 'dir_callback' => null, - 'ignore_files' => null, - 'ignore_dirs' => null, - 'ignore_vcs' => true, - 'min_depth' => null, - 'max_depth' => null - ); - - /** - * @var array - */ - protected $options; - - public function __construct() - { - $this->options = array(); - $class = get_class($this); - - // We build our options array ourselves, because possibly no class or config manifest exists at this point - do { - $this->options = array_merge(Object::static_lookup($class, 'default_options'), $this->options); - } while ($class = get_parent_class($class)); - } - - /** - * Returns an option value set on this instance. - * - * @param string $name - * @return mixed - */ - public function getOption($name) - { - if (!array_key_exists($name, $this->options)) { - throw new InvalidArgumentException("The option $name doesn't exist."); - } - - return $this->options[$name]; - } - - /** - * Set an option on this finder instance. See {@link SS_FileFinder} for the - * list of options available. - * - * @param string $name - * @param mixed $value - */ - public function setOption($name, $value) - { - if (!array_key_exists($name, $this->options)) { - throw new InvalidArgumentException("The option $name doesn't exist."); - } - - $this->options[$name] = $value; - } - - /** - * Sets several options at once. - * - * @param array $options - */ - public function setOptions(array $options) - { - foreach ($options as $k => $v) { - $this->setOption($k, $v); - } - } - - /** - * Finds all files matching the options within a directory. The search is - * performed depth first. - * - * @param string $base - * @return array - */ - public function find($base) - { - $paths = array(array(rtrim($base, '/'), 0)); - $found = array(); - - $fileCallback = $this->getOption('file_callback'); - $dirCallback = $this->getOption('dir_callback'); - - while ($path = array_shift($paths)) { - list($path, $depth) = $path; - - foreach (scandir($path) as $basename) { - if ($basename == '.' || $basename == '..') { - continue; - } - - if (is_dir("$path/$basename")) { - if (!$this->acceptDir($basename, "$path/$basename", $depth + 1)) { - continue; - } - - if ($dirCallback) { - call_user_func( - $dirCallback, - $basename, - "$path/$basename", - $depth + 1 - ); - } - - $paths[] = array("$path/$basename", $depth + 1); - } else { - if (!$this->acceptFile($basename, "$path/$basename", $depth)) { - continue; - } - - if ($fileCallback) { - call_user_func( - $fileCallback, - $basename, - "$path/$basename", - $depth - ); - } - - $found[] = "$path/$basename"; - } - } - } - - return $found; - } - - /** - * Returns TRUE if the directory should be traversed. This can be overloaded - * to customise functionality, or extended with callbacks. - * - * @param string $basename - * @param string $pathname - * @param int $depth - * @return bool - */ - protected function acceptDir($basename, $pathname, $depth) - { - if ($regex = $this->getOption('dir_regex')) { - if (!preg_match($regex, $basename)) { - return false; - } - } - - if ($this->getOption('ignore_vcs') && in_array($basename, self::$vcs_dirs)) { - return false; - } - - if ($ignore = $this->getOption('ignore_dirs')) { - if (in_array($basename, $ignore)) { - return false; - } - } - - if ($max = $this->getOption('max_depth')) { - if ($depth > $max) { - return false; - } - } - - if ($callback = $this->getOption('accept_callback')) { - if (!call_user_func($callback, $basename, $pathname, $depth)) { - return false; - } - } - - if ($callback = $this->getOption('accept_dir_callback')) { - if (!call_user_func($callback, $basename, $pathname, $depth)) { - return false; - } - } - - return true; - } - - /** - * Returns TRUE if the file should be included in the results. This can be - * overloaded to customise functionality, or extended via callbacks. - * - * @param string $basename - * @param string $pathname - * @param int $depth - * @return bool - */ - protected function acceptFile($basename, $pathname, $depth) - { - if ($regex = $this->getOption('name_regex')) { - if (!preg_match($regex, $basename)) { - return false; - } - } - - if ($ignore = $this->getOption('ignore_files')) { - if (in_array($basename, $ignore)) { - return false; - } - } - - if ($minDepth = $this->getOption('min_depth')) { - if ($depth < $minDepth) { - return false; - } - } - - if ($callback = $this->getOption('accept_callback')) { - if (!call_user_func($callback, $basename, $pathname, $depth)) { - return false; - } - } - - if ($callback = $this->getOption('accept_file_callback')) { - if (!call_user_func($callback, $basename, $pathname, $depth)) { - return false; - } - } - - return true; - } -} diff --git a/src/Assets/FileMigrationHelper.php b/src/Assets/FileMigrationHelper.php deleted file mode 100644 index af1d5c995..000000000 --- a/src/Assets/FileMigrationHelper.php +++ /dev/null @@ -1,127 +0,0 @@ -hasField('File', 'Filename')) { - return 0; - } - - // Set max time and memory limit - increase_time_limit_to(); - increase_memory_limit_to(); - - // Loop over all files - $count = 0; - $originalState = Versioned::get_reading_mode(); - Versioned::set_stage(Versioned::DRAFT); - $filenameMap = $this->getFilenameArray(); - foreach ($this->getFileQuery() as $file) { - // Get the name of the file to import - $filename = $filenameMap[$file->ID]; - $success = $this->migrateFile($base, $file, $filename); - if ($success) { - $count++; - } - } - Versioned::set_reading_mode($originalState); - return $count; - } - - /** - * 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 = $base . '/' . $legacyFilename; - if (!file_exists($path)) { - return false; - } - - - // Copy local file into this filesystem - $filename = $file->generateFilename(); - $result = $file->setFromLocalFile( - $path, - $filename, - null, - null, - array('conflict' => AssetStore::CONFLICT_OVERWRITE) - ); - - // Move file if the APL changes filename value - if ($result['Filename'] !== $filename) { - $file->setFilename($result['Filename']); - } - - // Save and publish - $file->write(); - $file->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); - return true; - } - - /** - * Get list of File dataobjects to import - * - * @return DataList - */ - protected function getFileQuery() - { - // Select all records which have a Filename value, but not FileFilename. - /** @skipUpgrade */ - return File::get() - ->exclude('ClassName', ['SilverStripe\\Assets\\Folder', 'Folder']) - ->filter('FileFilename', array('', null)) - ->where('"File"."Filename" IS NOT NULL AND "File"."Filename" != \'\''); // Non-orm field - } - - /** - * Get map of File IDs to legacy filenames - * - * @return array - */ - protected function getFilenameArray() - { - // Convert original query, ensuring the legacy "Filename" is included in the result - /** @skipUpgrade */ - return $this - ->getFileQuery() - ->dataQuery() - ->selectFromTable('File', array('ID', 'Filename')) - ->execute() - ->map(); // map ID to Filename - } -} diff --git a/src/Assets/FileNameFilter.php b/src/Assets/FileNameFilter.php deleted file mode 100644 index c73572e7d..000000000 --- a/src/Assets/FileNameFilter.php +++ /dev/null @@ -1,140 +0,0 @@ - - * FileNameFilter: - * default_use_transliterator: false - * default_replacements: - * - * - * See {@link URLSegmentFilter} for a more generic implementation. - */ -class FileNameFilter extends Object -{ - - /** - * @config - * @var Boolean - */ - private static $default_use_transliterator = true; - - /** - * @config - * @var array See {@link setReplacements()}. - */ - private static $default_replacements = array( - '/\s/' => '-', // remove whitespace - '/_/' => '-', // underscores to dashes - '/[^A-Za-z0-9+.\-]+/' => '', // remove non-ASCII chars, only allow alphanumeric plus dash and dot - '/[\-]{2,}/' => '-', // remove duplicate dashes - '/^[\.\-_]+/' => '', // Remove all leading dots, dashes or underscores - ); - - /** - * @var array See {@link setReplacements()} - */ - public $replacements = array(); - - /** - * Depending on the applied replacement rules, this method - * might result in an empty string. In this case, {@link getDefaultName()} - * will be used to return a randomly generated file name, while retaining its extension. - * - * @param string $name including extension (not path). - * @return string A filtered filename - */ - public function filter($name) - { - $ext = pathinfo($name, PATHINFO_EXTENSION); - - $transliterator = $this->getTransliterator(); - if ($transliterator) { - $name = $transliterator->toASCII($name); - } - foreach ($this->getReplacements() as $regex => $replace) { - $name = preg_replace($regex, $replace, $name); - } - - // Safeguard against empty file names - $nameWithoutExt = pathinfo($name, PATHINFO_FILENAME); - if (empty($nameWithoutExt)) { - $name = $this->getDefaultName(); - $name .= $ext ? '.' . $ext : ''; - } - - return $name; - } - - /** - * Take care not to add replacements which might invalidate the file structure, - * e.g. removing dots will remove file extension information. - * - * @param array $r Map of find/replace used for preg_replace(). - */ - public function setReplacements($r) - { - $this->replacements = $r; - } - - /** - * @return array - */ - public function getReplacements() - { - return ($this->replacements) ? $this->replacements : (array)$this->config()->default_replacements; - } - - /** - * Transliterator instance, or false to disable. - * If null will use default. - * - * @var Transliterator|false - */ - protected $transliterator; - - /** - * @return Transliterator - */ - public function getTransliterator() - { - if ($this->transliterator === null && $this->config()->default_use_transliterator) { - $this->transliterator = Transliterator::create(); - } - return $this->transliterator; - } - - /** - * @param Transliterator|false $t - */ - public function setTransliterator($t) - { - $this->transliterator = $t; - } - - /** - * @return String File name without extension - */ - public function getDefaultName() - { - return (string)uniqid(); - } -} diff --git a/src/Assets/Filesystem.php b/src/Assets/Filesystem.php deleted file mode 100644 index f5f6eae29..000000000 --- a/src/Assets/Filesystem.php +++ /dev/null @@ -1,183 +0,0 @@ -folder_create_mask); - } - } - - /** - * Remove a directory and all subdirectories and files. - * - * @param String $folder Absolute folder path - * @param Boolean $contentsOnly If this is true then the contents of the folder will be removed but not the - * folder itself - */ - public static function removeFolder($folder, $contentsOnly = false) - { - - // remove a file encountered by a recursive call. - if (is_file($folder) || is_link($folder)) { - unlink($folder); - } else { - $dir = opendir($folder); - while ($file = readdir($dir)) { - if (($file == '.' || $file == '..')) { - continue; - } else { - self::removeFolder($folder . '/' . $file); - } - } - closedir($dir); - if (!$contentsOnly) { - rmdir($folder); - } - } - } - - /** - * Remove a directory, but only if it is empty. - * - * @param string $folder Absolute folder path - * @param boolean $recursive Remove contained empty folders before attempting to remove this one - * @return boolean True on success, false on failure. - */ - public static function remove_folder_if_empty($folder, $recursive = true) - { - if (!is_readable($folder)) { - return false; - } - $handle = opendir($folder); - while (false !== ($entry = readdir($handle))) { - if ($entry != "." && $entry != "..") { - // if an empty folder is detected, remove that one first and move on - if ($recursive && is_dir($entry) && self::remove_folder_if_empty($entry)) { - continue; - } - // if a file was encountered, or a subdirectory was not empty, return false. - return false; - } - } - // if we are still here, the folder is empty. - rmdir($folder); - return true; - } - - /** - * Cleanup function to reset all the Filename fields. Visit File/fixfiles to call. - * - * @deprecated 5.0 - */ - public function fixfiles() - { - Deprecation::notice('5.0'); - 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. - * - * @param string $folder The folder, relative to the site root - * @param array $extensionList An option array of file extensions to limit the search to - * @return string Same as filemtime() format. - */ - public static function folderModTime($folder, $extensionList = null) - { - $modTime = 0; - if (!Filesystem::isAbsolute($folder)) { - $folder = Director::baseFolder() . '/' . $folder; - } - - $items = scandir($folder); - foreach ($items as $item) { - if ($item[0] != '.') { - // Recurse into folders - if (is_dir("$folder/$item")) { - $modTime = max($modTime, self::folderModTime("$folder/$item", $extensionList)); - - // Check files - } else { - $extension = null; - if ($extensionList) { - $extension = strtolower(substr($item, strrpos($item, '.')+1)); - } - if (!$extensionList || in_array($extension, $extensionList)) { - $modTime = max($modTime, filemtime("$folder/$item")); - } - } - } - } - - //if(!$recursiveCall) self::$cache_folderModTime[$cacheID] = $modTime; - return $modTime; - } - - /** - * Returns true if the given filename is an absolute file reference. - * Works on Linux and Windows. - * - * @param String $filename Absolute or relative filename, with or without path. - * @return Boolean - */ - public static function isAbsolute($filename) - { - if ($_ENV['OS'] == "Windows_NT" || $_SERVER['WINDIR']) { - return $filename[1] == ':' && $filename[2] == '/'; - } else { - return $filename[0] == '/'; - } - } -} diff --git a/src/Assets/Flysystem/AssetAdapter.php b/src/Assets/Flysystem/AssetAdapter.php deleted file mode 100644 index d594c8d28..000000000 --- a/src/Assets/Flysystem/AssetAdapter.php +++ /dev/null @@ -1,153 +0,0 @@ - [ - 'public' => 0744, - 'private' => 0700, - ], - 'dir' => [ - 'public' => 0755, - 'private' => 0700, - ] - ); - - public function __construct($root = null, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS) - { - // Get root path, and ensure that this exists and is safe - $root = $this->findRoot($root); - Filesystem::makeFolder($root); - $root = realpath($root); - - // Override permissions with config - $permissions = $this->config()->get('file_permissions'); - parent::__construct($root, $writeFlags, $linkHandling, $permissions); - - // Configure server - $this->configureServer(); - } - - /** - * Determine the root folder absolute system path - * - * @param string $root - * @return string - */ - protected function findRoot($root) - { - // Empty root will set the path to assets - if (!$root) { - throw new \InvalidArgumentException("Missing argument for root path"); - } - - // Substitute leading ./ with BASE_PATH - if (strpos($root, './') === 0) { - return BASE_PATH . substr($root, 1); - } - - // Substitute leading ./ with parent of BASE_PATH, in case storage is outside of the webroot. - if (strpos($root, '../') === 0) { - return dirname(BASE_PATH) . substr($root, 2); - } - - return $root; - } - - /** - * Force flush and regeneration of server files - */ - public function flush() - { - $this->configureServer(true); - } - - /** - * Configure server files for this store - * - * @param bool $forceOverwrite Force regeneration even if files already exist - * @throws \Exception - */ - protected function configureServer($forceOverwrite = false) - { - // Get server type - $type = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '*'; - list($type) = explode('/', strtolower($type)); - - // Determine configurations to write - $rules = $this->config()->get('server_configuration'); - if (empty($rules[$type])) { - return; - } - $configurations = $rules[$type]; - - // Apply each configuration - $config = new FlysystemConfig(); - $config->set('visibility', 'private'); - foreach ($configurations as $file => $template) { - if ($forceOverwrite || !$this->has($file)) { - // Evaluate file - $content = $this->renderTemplate($template); - $success = $this->write($file, $content, $config); - if (!$success) { - throw new \Exception("Error writing server configuration file \"{$file}\""); - } - } - } - } - - /** - * Render server configuration file from a template file - * - * @param string $template - * @return string Rendered results - */ - protected function renderTemplate($template) - { - // Build allowed extensions - $allowedExtensions = new ArrayList(); - foreach (File::config()->allowed_extensions as $extension) { - if ($extension) { - $allowedExtensions->push(new ArrayData(array( - 'Extension' => preg_quote($extension) - ))); - } - } - - $viewer = new SSViewer(array($template)); - return (string)$viewer->process(new ArrayData(array( - 'AllowedExtensions' => $allowedExtensions - ))); - } -} diff --git a/src/Assets/Flysystem/FlysystemAssetStore.php b/src/Assets/Flysystem/FlysystemAssetStore.php deleted file mode 100644 index 62be01d09..000000000 --- a/src/Assets/Flysystem/FlysystemAssetStore.php +++ /dev/null @@ -1,910 +0,0 @@ - 'private' - ); - - /** - * Assign new flysystem backend - * - * @param Filesystem $filesystem - * @return $this - */ - public function setPublicFilesystem(Filesystem $filesystem) - { - if (!$filesystem->getAdapter() instanceof PublicAdapter) { - throw new InvalidArgumentException("Configured adapter must implement PublicAdapter"); - } - $this->publicFilesystem = $filesystem; - return $this; - } - - /** - * Get the currently assigned flysystem backend - * - * @return Filesystem - * @throws LogicException - */ - public function getPublicFilesystem() - { - if (!$this->publicFilesystem) { - throw new LogicException("Filesystem misconfiguration error"); - } - return $this->publicFilesystem; - } - - /** - * Assign filesystem to use for non-public files - * - * @param Filesystem $filesystem - * @return $this - */ - public function setProtectedFilesystem(Filesystem $filesystem) - { - if (!$filesystem->getAdapter() instanceof ProtectedAdapter) { - throw new InvalidArgumentException("Configured adapter must implement ProtectedAdapter"); - } - $this->protectedFilesystem = $filesystem; - return $this; - } - - /** - * Get filesystem to use for non-public files - * - * @return Filesystem - * @throws Exception - */ - public function getProtectedFilesystem() - { - if (!$this->protectedFilesystem) { - throw new Exception("Filesystem misconfiguration error"); - } - return $this->protectedFilesystem; - } - - /** - * Return the store that contains the given fileID - * - * @param string $fileID Internal file identifier - * @return Filesystem - */ - protected function getFilesystemFor($fileID) - { - if ($this->getPublicFilesystem()->has($fileID)) { - return $this->getPublicFilesystem(); - } - - if ($this->getProtectedFilesystem()->has($fileID)) { - return $this->getProtectedFilesystem(); - } - - return null; - } - - public function getCapabilities() - { - return array( - 'visibility' => array( - self::VISIBILITY_PUBLIC, - self::VISIBILITY_PROTECTED - ), - 'conflict' => array( - self::CONFLICT_EXCEPTION, - self::CONFLICT_OVERWRITE, - self::CONFLICT_RENAME, - self::CONFLICT_USE_EXISTING - ) - ); - } - - public function getVisibility($filename, $hash) - { - $fileID = $this->getFileID($filename, $hash); - if ($this->getPublicFilesystem()->has($fileID)) { - return self::VISIBILITY_PUBLIC; - } - - if ($this->getProtectedFilesystem()->has($fileID)) { - return self::VISIBILITY_PROTECTED; - } - - return null; - } - - - public function getAsStream($filename, $hash, $variant = null) - { - $fileID = $this->getFileID($filename, $hash, $variant); - return $this - ->getFilesystemFor($fileID) - ->readStream($fileID); - } - - public function getAsString($filename, $hash, $variant = null) - { - $fileID = $this->getFileID($filename, $hash, $variant); - return $this - ->getFilesystemFor($fileID) - ->read($fileID); - } - - public function getAsURL($filename, $hash, $variant = null, $grant = true) - { - if ($grant) { - $this->grant($filename, $hash); - } - $fileID = $this->getFileID($filename, $hash, $variant); - - // Check with filesystem this asset exists in - $public = $this->getPublicFilesystem(); - $protected = $this->getProtectedFilesystem(); - if ($public->has($fileID) || !$protected->has($fileID)) { - /** @var PublicAdapter $publicAdapter */ - $publicAdapter = $public->getAdapter(); - return $publicAdapter->getPublicUrl($fileID); - } else { - /** @var ProtectedAdapter $protectedAdapter */ - $protectedAdapter = $protected->getAdapter(); - return $protectedAdapter->getProtectedUrl($fileID); - } - } - - public function setFromLocalFile($path, $filename = null, $hash = null, $variant = null, $config = array()) - { - // Validate this file exists - if (!file_exists($path)) { - throw new InvalidArgumentException("$path does not exist"); - } - - // Get filename to save to - if (empty($filename)) { - $filename = basename($path); - } - - // Callback for saving content - $callback = function (Filesystem $filesystem, $fileID) use ($path) { - // Read contents as string into flysystem - $handle = fopen($path, 'r'); - if ($handle === false) { - throw new InvalidArgumentException("$path could not be opened for reading"); - } - $result = $filesystem->putStream($fileID, $handle); - fclose($handle); - return $result; - }; - - // When saving original filename, generate hash - if (!$variant) { - $hash = sha1_file($path); - } - - // Submit to conflict check - return $this->writeWithCallback($callback, $filename, $hash, $variant, $config); - } - - public function setFromString($data, $filename, $hash = null, $variant = null, $config = array()) - { - // Callback for saving content - $callback = function (Filesystem $filesystem, $fileID) use ($data) { - return $filesystem->put($fileID, $data); - }; - - // When saving original filename, generate hash - if (!$variant) { - $hash = sha1($data); - } - - // Submit to conflict check - return $this->writeWithCallback($callback, $filename, $hash, $variant, $config); - } - - public function setFromStream($stream, $filename, $hash = null, $variant = null, $config = array()) - { - // If the stream isn't rewindable, write to a temporary filename - if (!$this->isSeekableStream($stream)) { - $path = $this->getStreamAsFile($stream); - $result = $this->setFromLocalFile($path, $filename, $hash, $variant, $config); - unlink($path); - return $result; - } - - // Callback for saving content - $callback = function (Filesystem $filesystem, $fileID) use ($stream) { - return $filesystem->putStream($fileID, $stream); - }; - - // When saving original filename, generate hash - if (!$variant) { - $hash = $this->getStreamSHA1($stream); - } - - // Submit to conflict check - return $this->writeWithCallback($callback, $filename, $hash, $variant, $config); - } - - public function delete($filename, $hash) - { - $fileID = $this->getFileID($filename, $hash); - $protected = $this->deleteFromFilesystem($fileID, $this->getProtectedFilesystem()); - $public = $this->deleteFromFilesystem($fileID, $this->getPublicFilesystem()); - return $protected || $public; - } - - /** - * 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 - */ - protected function deleteFromFilesystem($fileID, Filesystem $filesystem) - { - $deleted = false; - foreach ($this->findVariants($fileID, $filesystem) as $nextID) { - $filesystem->delete($nextID); - $deleted = true; - } - - // Truncate empty dirs - $this->truncateDirectory(dirname($fileID), $filesystem); - - return $deleted; - } - - /** - * Clear directory if it's empty - * - * @param string $dirname Name of directory - * @param Filesystem $filesystem - */ - protected function truncateDirectory($dirname, Filesystem $filesystem) - { - if ($dirname - && ltrim(dirname($dirname), '.') - && ! $this->config()->get('keep_empty_dirs') - && ! $filesystem->listContents($dirname) - ) { - $filesystem->deleteDir($dirname); - } - } - - /** - * 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 - */ - protected function findVariants($fileID, Filesystem $filesystem) - { - $dirname = ltrim(dirname($fileID), '.'); - foreach ($filesystem->listContents($dirname) 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) - { - $fileID = $this->getFileID($filename, $hash); - $protected = $this->getProtectedFilesystem(); - $public = $this->getPublicFilesystem(); - $this->moveBetweenFilesystems($fileID, $protected, $public); - } - - public function protect($filename, $hash) - { - $fileID = $this->getFileID($filename, $hash); - $public = $this->getPublicFilesystem(); - $protected = $this->getProtectedFilesystem(); - $this->moveBetweenFilesystems($fileID, $public, $protected); - } - - /** - * Move a file (and its associative variants) between filesystems - * - * @param string $fileID - * @param Filesystem $from - * @param Filesystem $to - */ - protected function moveBetweenFilesystems($fileID, Filesystem $from, Filesystem $to) - { - foreach ($this->findVariants($fileID, $from) as $nextID) { - // Copy via stream - $stream = $from->readStream($nextID); - $to->putStream($nextID, $stream); - fclose($stream); - $from->delete($nextID); - } - - // Truncate empty dirs - $this->truncateDirectory(dirname($fileID), $from); - } - - public function grant($filename, $hash) - { - $fileID = $this->getFileID($filename, $hash); - $granted = Session::get(self::GRANTS_SESSION) ?: array(); - $granted[$fileID] = true; - Session::set(self::GRANTS_SESSION, $granted); - } - - public function revoke($filename, $hash) - { - $fileID = $this->getFileID($filename, $hash); - $granted = Session::get(self::GRANTS_SESSION) ?: array(); - unset($granted[$fileID]); - if ($granted) { - Session::set(self::GRANTS_SESSION, $granted); - } else { - Session::clear(self::GRANTS_SESSION); - } - } - - public function canView($filename, $hash) - { - $fileID = $this->getFileID($filename, $hash); - if ($this->getProtectedFilesystem()->has($fileID)) { - return $this->isGranted($fileID); - } - return true; - } - - /** - * Determine if a grant exists for the given FileID - * - * @param string $fileID - * @return bool - */ - protected function isGranted($fileID) - { - // Since permissions are applied to the non-variant only, - // map back to the original file before checking - $originalID = $this->removeVariant($fileID); - $granted = Session::get(self::GRANTS_SESSION) ?: array(); - return !empty($granted[$originalID]); - } - - /** - * get sha1 hash from stream - * - * @param resource $stream - * @return string str1 hash - */ - protected function getStreamSHA1($stream) - { - Util::rewindStream($stream); - $context = hash_init('sha1'); - hash_update_stream($context, $stream); - return hash_final($context); - } - - /** - * Get stream as a file - * - * @param resource $stream - * @return string Filename of resulting stream content - * @throws Exception - */ - protected function getStreamAsFile($stream) - { - // Get temporary file and name - $file = tempnam(sys_get_temp_dir(), 'ssflysystem'); - $buffer = fopen($file, 'w'); - if (!$buffer) { - 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 Exception("Could not write stream to temporary file"); - } - - return $file; - } - - /** - * Determine if this stream is seekable - * - * @param resource $stream - * @return bool True if this stream is seekable - */ - protected function isSeekableStream($stream) - { - return Util::isSeekableStream($stream); - } - - /** - * Invokes the conflict resolution scheme on the given content, and invokes a callback if - * the storage request is approved. - * - * @param callable $callback Will be invoked and passed a fileID if the file should be stored - * @param string $filename Name for the resulting file - * @param string $hash SHA1 of the original file content - * @param string $variant Variant to write - * @param array $config Write options. {@see AssetStore} - * @return array Tuple associative array (Filename, Hash, Variant) - * @throws Exception - */ - protected function writeWithCallback($callback, $filename, $hash, $variant = null, $config = array()) - { - // Set default conflict resolution - if (empty($config['conflict'])) { - $conflictResolution = $this->getDefaultConflictResolution($variant); - } else { - $conflictResolution = $config['conflict']; - } - - // Validate parameters - if ($variant && $conflictResolution === AssetStore::CONFLICT_RENAME) { - // As variants must follow predictable naming rules, they should not be dynamically renamed - throw new InvalidArgumentException("Rename cannot be used when writing variants"); - } - if (!$filename) { - throw new InvalidArgumentException("Filename is missing"); - } - if (!$hash) { - throw new InvalidArgumentException("File hash is missing"); - } - - $filename = $this->cleanFilename($filename); - $fileID = $this->getFileID($filename, $hash, $variant); - - // Check conflict resolution scheme - $resolvedID = $this->resolveConflicts($conflictResolution, $fileID); - if ($resolvedID !== false) { - // Check if source file already exists on the filesystem - $mainID = $this->getFileID($filename, $hash); - $filesystem = $this->getFilesystemFor($mainID); - - // If writing a new file use the correct visibility - if (!$filesystem) { - // Default to public store unless requesting protected store - if (isset($config['visibility']) && $config['visibility'] === self::VISIBILITY_PROTECTED) { - $filesystem = $this->getProtectedFilesystem(); - } else { - $filesystem = $this->getPublicFilesystem(); - } - } - - // Submit and validate result - $result = $callback($filesystem, $resolvedID); - if (!$result) { - throw new Exception("Could not save {$filename}"); - } - - // in case conflict resolution renamed the file, return the renamed - $filename = $this->getOriginalFilename($resolvedID); - } elseif (empty($variant)) { - // If deferring to the existing file, return the sha of the existing file, - // unless we are writing a variant (which has the same hash value as its original file) - $stream = $this - ->getFilesystemFor($fileID) - ->readStream($fileID); - $hash = $this->getStreamSHA1($stream); - } - - return array( - 'Filename' => $filename, - 'Hash' => $hash, - 'Variant' => $variant - ); - } - - /** - * Choose a default conflict resolution - * - * @param string $variant - * @return string - */ - protected function getDefaultConflictResolution($variant) - { - // If using new naming scheme (segment by hash) it's normally safe to overwrite files. - // Variants are also normally safe to overwrite, since lazy-generation is implemented at a higher level. - $legacy = $this->useLegacyFilenames(); - if (!$legacy || $variant) { - return AssetStore::CONFLICT_OVERWRITE; - } - - // Legacy behaviour is to rename - return AssetStore::CONFLICT_RENAME; - } - - /** - * Determine if legacy filenames should be used. These do not have hash path parts. - * - * @return bool - */ - protected function useLegacyFilenames() - { - return $this->config()->get('legacy_filenames'); - } - - public function getMetadata($filename, $hash, $variant = null) - { - $fileID = $this->getFileID($filename, $hash, $variant); - $filesystem = $this->getFilesystemFor($fileID); - if ($filesystem) { - return $filesystem->getMetadata($fileID); - } - return null; - } - - public function getMimeType($filename, $hash, $variant = null) - { - $fileID = $this->getFileID($filename, $hash, $variant); - $filesystem = $this->getFilesystemFor($fileID); - if ($filesystem) { - return $filesystem->getMimetype($fileID); - } - return null; - } - - public function exists($filename, $hash, $variant = null) - { - $fileID = $this->getFileID($filename, $hash, $variant); - $filesystem = $this->getFilesystemFor($fileID); - return !empty($filesystem); - } - - /** - * Determine the path that should be written to, given the conflict resolution scheme - * - * @param string $conflictResolution - * @param string $fileID - * @return string|false Safe filename to write to. If false, then don't write, and use existing file. - * @throws Exception - */ - protected function resolveConflicts($conflictResolution, $fileID) - { - // If overwrite is requested, simply put - if ($conflictResolution === AssetStore::CONFLICT_OVERWRITE) { - return $fileID; - } - - // Otherwise, check if this exists - $exists = $this->getFilesystemFor($fileID); - if (!$exists) { - return $fileID; - } - - // Flysystem defaults to use_existing - switch ($conflictResolution) { - // Throw tantrum - case static::CONFLICT_EXCEPTION: { - throw new InvalidArgumentException("File already exists at path {$fileID}"); - } - - // Rename - case static::CONFLICT_RENAME: { - foreach ($this->fileGeneratorFor($fileID) as $candidate) { - if (!$this->getFilesystemFor($candidate)) { - return $candidate; - } - } - - throw new InvalidArgumentException("File could not be renamed with path {$fileID}"); - } - - // Use existing file - case static::CONFLICT_USE_EXISTING: - default: { - return false; - } - } - } - - /** - * Get an asset renamer for the given filename. - * - * @param string $fileID Adapter specific identifier for this file/version - * @return AssetNameGenerator - */ - protected function fileGeneratorFor($fileID) - { - return Injector::inst()->createWithArgs('AssetNameGenerator', array($fileID)); - } - - /** - * Performs filename cleanup before sending it back. - * - * This name should not contain hash or variants. - * - * @param string $filename - * @return string - */ - protected function cleanFilename($filename) - { - // Since we use double underscore to delimit variants, eradicate them from filename - return preg_replace('/_{2,}/', '_', $filename); - } - - /** - * 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 - */ - protected function getOriginalFilename($fileID) - { - // Remove variant - $originalID = $this->removeVariant($fileID); - - // Remove hash (unless using legacy filenames, without hash) - if ($this->useLegacyFilenames()) { - return $originalID; - } else { - return preg_replace( - '/(?[a-zA-Z0-9]{10}\\/)(?[^\\/]+)$/', - '$2', - $originalID - ); - } - } - - /** - * Remove variant from a fileID - * - * @param string $fileID - * @return string FileID without variant - */ - protected function removeVariant($fileID) - { - // Check variant - if (preg_match('/^(?((?[^\\.]+)(?.*)$/', $fileID, $matches)) { - return $matches['before'] . $matches['after']; - } - // There is no variant, so return original value - return $fileID; - } - - /** - * Map file tuple (hash, name, variant) to a filename to be used by flysystem - * - * The resulting file will look something like my/directory/EA775CB4D4/filename__variant.jpg - * - * @param string $filename Name of file - * @param string $hash Hash of original file - * @param string $variant (if given) - * @return string Adapter specific identifier for this file/version - */ - protected function getFileID($filename, $hash, $variant = null) - { - // Since we use double underscore to delimit variants, eradicate them from filename - $filename = $this->cleanFilename($filename); - $name = basename($filename); - - // Split extension - $extension = null; - if (($pos = strpos($name, '.')) !== false) { - $extension = substr($name, $pos); - $name = substr($name, 0, $pos); - } - - // Unless in legacy mode, inject hash just prior to the filename - if ($this->useLegacyFilenames()) { - $fileID = $name; - } else { - $fileID = substr($hash, 0, 10) . '/' . $name; - } - - // Add directory - $dirname = ltrim(dirname($filename), '.'); - if ($dirname) { - $fileID = $dirname . '/' . $fileID; - } - - // Add variant - if ($variant) { - $fileID .= '__' . $variant; - } - - // Add extension - if ($extension) { - $fileID .= $extension; - } - - return $fileID; - } - - /** - * Ensure each adapter re-generates its own server configuration files - */ - public static function flush() - { - // Ensure that this instance is constructed on flush, thus forcing - // bootstrapping of necessary .htaccess / web.config files - $instance = singleton('AssetStore'); - if ($instance instanceof FlysystemAssetStore) { - $publicAdapter = $instance->getPublicFilesystem()->getAdapter(); - if ($publicAdapter instanceof AssetAdapter) { - $publicAdapter->flush(); - } - $protectedAdapter = $instance->getProtectedFilesystem()->getAdapter(); - if ($protectedAdapter instanceof AssetAdapter) { - $protectedAdapter->flush(); - } - } - } - - public function getResponseFor($asset) - { - // Check if file exists - $filesystem = $this->getFilesystemFor($asset); - if (!$filesystem) { - return $this->createMissingResponse(); - } - - // Block directory access - if ($filesystem->get($asset) instanceof Directory) { - return $this->createDeniedResponse(); - } - - // Deny if file is protected and denied - if ($filesystem === $this->getProtectedFilesystem() && !$this->isGranted($asset)) { - return $this->createDeniedResponse(); - } - - // Serve up file response - return $this->createResponseFor($filesystem, $asset); - } - - /** - * Generate an {@see HTTPResponse} for the given file from the source filesystem - * @param FilesystemInterface $flysystem - * @param string $fileID - * @return HTTPResponse - */ - protected function createResponseFor(FilesystemInterface $flysystem, $fileID) - { - // Build response body - // @todo: gzip / buffer response? - $body = $flysystem->read($fileID); - $mime = $flysystem->getMimetype($fileID); - $response = new HTTPResponse($body, 200); - - // Add headers - $response->addHeader('Content-Type', $mime); - $headers = $this->config()->get('file_response_headers'); - foreach ($headers as $header => $value) { - $response->addHeader($header, $value); - } - return $response; - } - - /** - * Generate a response for requests to a denied protected file - * - * @return HTTPResponse - */ - protected function createDeniedResponse() - { - $code = (int)$this->config()->get('denied_response_code'); - return $this->createErrorResponse($code); - } - - /** - * Generate a response for missing file requests - * - * @return HTTPResponse - */ - protected function createMissingResponse() - { - $code = (int)$this->config()->get('missing_response_code'); - return $this->createErrorResponse($code); - } - - /** - * Create a response with the given error code - * - * @param int $code - * @return HTTPResponse - */ - protected function createErrorResponse($code) - { - $response = new HTTPResponse('', $code); - - // Show message in dev - if (!Director::isLive()) { - $response->setBody($response->getStatusDescription()); - } - - return $response; - } -} diff --git a/src/Assets/Flysystem/GeneratedAssetHandler.php b/src/Assets/Flysystem/GeneratedAssetHandler.php deleted file mode 100644 index 9eb73e8f4..000000000 --- a/src/Assets/Flysystem/GeneratedAssetHandler.php +++ /dev/null @@ -1,116 +0,0 @@ -assetStore = $store; - return $this; - } - - /** - * Get the asset backend - * - * @return Filesystem - * @throws Exception - */ - public function getFilesystem() - { - if (!$this->assetStore) { - throw new Exception("Filesystem misconfiguration error"); - } - return $this->assetStore; - } - - public function getContentURL($filename, $callback = null) - { - $result = $this->checkOrCreate($filename, $callback); - if (!$result) { - return null; - } - /** @var PublicAdapter $adapter */ - $adapter = $this - ->getFilesystem() - ->getAdapter(); - return $adapter->getPublicUrl($filename); - } - - public function getContent($filename, $callback = null) - { - $result = $this->checkOrCreate($filename, $callback); - if (!$result) { - return null; - } - return $this - ->getFilesystem() - ->read($filename); - } - - /** - * Check if the file exists or that the $callback provided was able to regenerate it. - * - * @param string $filename - * @param callable $callback - * @return bool Whether or not the file exists - * @throws Exception If an error has occurred during save - */ - protected function checkOrCreate($filename, $callback = null) - { - // Check if there is an existing asset - if ($this->getFilesystem()->has($filename)) { - return true; - } - - if (!$callback) { - return false; - } - - // Invoke regeneration and save - $content = call_user_func($callback); - $this->setContent($filename, $content); - return true; - } - - public function setContent($filename, $content) - { - // Store content - $result = $this - ->getFilesystem() - ->put($filename, $content); - - if (!$result) { - throw new Exception("Error regenerating file \"{$filename}\""); - } - } - - public function removeContent($filename) - { - if ($this->getFilesystem()->has($filename)) { - $handler = $this->getFilesystem()->get($filename); - $handler->delete(); - } - } -} diff --git a/src/Assets/Flysystem/ProtectedAdapter.php b/src/Assets/Flysystem/ProtectedAdapter.php deleted file mode 100644 index 33f76df3d..000000000 --- a/src/Assets/Flysystem/ProtectedAdapter.php +++ /dev/null @@ -1,20 +0,0 @@ - array( - '.htaccess' => "SilverStripe\\Assets\\Flysystem\\ProtectedAssetAdapter_HTAccess" - ), - 'microsoft-iis' => array( - 'web.config' => "SilverStripe\\Assets\\Flysystem\\ProtectedAssetAdapter_WebConfig" - ) - ); - - protected function findRoot($root) - { - // Use explicitly defined path - if ($root) { - return parent::findRoot($root); - } - - // Use environment defined path or default location is under assets - if ($path = getenv('SS_PROTECTED_ASSETS_PATH')) { - return $path; - } - - // Default location - return ASSETS_PATH . '/' . Config::inst()->get(__CLASS__, 'secure_folder'); - } - - /** - * Provide secure downloadable - * - * @param string $path - * @return string|null - */ - public function getProtectedUrl($path) - { - // Public URLs are handled via a request handler within /assets. - // If assets are stored locally, then asset paths of protected files should be equivalent. - return Controller::join_links(Director::baseURL(), ASSETS_DIR, $path); - } -} diff --git a/src/Assets/Flysystem/PublicAdapter.php b/src/Assets/Flysystem/PublicAdapter.php deleted file mode 100644 index 0770e246c..000000000 --- a/src/Assets/Flysystem/PublicAdapter.php +++ /dev/null @@ -1,20 +0,0 @@ - array( - '.htaccess' => "SilverStripe\\Assets\\Flysystem\\PublicAssetAdapter_HTAccess" - ), - 'microsoft-iis' => array( - 'web.config' => "SilverStripe\\Assets\\Flysystem\\PublicAssetAdapter_WebConfig" - ) - ); - - protected function findRoot($root) - { - if ($root) { - $path = parent::findRoot($root); - } else { - $path = ASSETS_PATH; - } - - // Detect segment between root directory and assets root - if (stripos($path, BASE_PATH) === 0) { - $this->parentUrlPrefix = substr($path, strlen(BASE_PATH)); - } else { - $this->parentUrlPrefix = ASSETS_DIR; - } - return $path; - } - - /** - * Provide downloadable url - * - * @param string $path - * @return string|null - */ - public function getPublicUrl($path) - { - return Controller::join_links(Director::baseURL(), $this->parentUrlPrefix, $path); - } -} diff --git a/src/Assets/Folder.php b/src/Assets/Folder.php deleted file mode 100644 index a9458f47d..000000000 --- a/src/Assets/Folder.php +++ /dev/null @@ -1,355 +0,0 @@ -isInDB(); - } - - /** - * Find the given folder or create it as a database record - * - * @param string $folderPath Directory path relative to assets root - * @return Folder|null - */ - public static function find_or_make($folderPath) - { - // replace leading and trailing slashes - $folderPath = preg_replace('/^\/?(.*)\/?$/', '$1', trim($folderPath)); - $parts = explode("/", $folderPath); - - $parentID = 0; - $item = null; - $filter = FileNameFilter::create(); - foreach ($parts as $part) { - if (!$part) { - continue; // happens for paths with a trailing slash - } - - // Ensure search includes folders with illegal characters removed, but - // err in favour of matching existing folders if $folderPath - // includes illegal characters itself. - $partSafe = $filter->filter($part); - $item = Folder::get()->filter(array( - 'ParentID' => $parentID, - 'Name' => array($partSafe, $part) - ))->first(); - - if (!$item) { - $item = new Folder(); - $item->ParentID = $parentID; - $item->Name = $partSafe; - $item->Title = $part; - $item->write(); - } - $parentID = $item->ID; - } - - return $item; - } - - public function onBeforeDelete() - { - foreach ($this->AllChildren() as $child) { - $child->delete(); - } - - parent::onBeforeDelete(); - } - - /** - * Return the relative URL of an icon for this file type - * - * @return string - */ - public function getIcon() - { - return FRAMEWORK_DIR . "/client/images/app_icons/folder_icon_large.png"; - } - - /** - * Override setting the Title of Folders to that Name and Title are always in sync. - * Note that this is not appropriate for files, because someone might want to create a human-readable name - * of a file that is different from its name on disk. But folders should always match their name on disk. - * - * @param string $title - * @return $this - */ - public function setTitle($title) - { - $this->setField('Title', $title); - $this->setField('Name', $title); - - return $this; - } - - /** - * Get the folder title - * - * @return string - */ - public function getTitle() - { - return $this->Name; - } - - /** - * A folder doesn't have a (meaningful) file size. - * - * @return null - */ - public function getSize() - { - return null; - } - - /** - * Returns all children of this folder - * - * @return DataList - */ - public function myChildren() - { - return File::get()->filter("ParentID", $this->ID); - } - - /** - * Returns true if this folder has children - * - * @return bool - */ - public function hasChildren() - { - return $this->myChildren()->exists(); - } - - /** - * Returns true if this folder has children - * - * @return bool - */ - public function hasChildFolders() - { - return $this->ChildFolders()->exists(); - } - - /** - * Return the FieldList used to edit this folder in the CMS. - * You can modify this FieldList by subclassing folder, or by creating a {@link DataExtension} - * and implemeting updateCMSFields(FieldList $fields) on that extension. - * - * @return FieldList - */ - public function getCMSFields() - { - // Don't show readonly path until we can implement parent folder selection, - // it's too confusing when readonly (makes sense for files only). - - $width = (int)Image::config()->get('asset_preview_width'); - $previewLink = Convert::raw2att($this->ScaleMaxWidth($width)->getIcon()); - $image = ""; - - $content = Tab::create( - 'Main', - HeaderField::create('TitleHeader', $this->Title, 1) - ->addExtraClass('editor__heading'), - LiteralField::create("IconFull", $image) - ->addExtraClass('editor__file-preview'), - TabSet::create( - 'Editor', - Tab::create( - 'Details', - TextField::create("Name", $this->fieldLabel('Filename')) - ) - ), - HiddenField::create('ID', $this->ID) - ); - - $fields = FieldList::create(TabSet::create('Root', $content)); - - $this->extend('updateCMSFields', $fields); - - return $fields; - } - - /** - * Get the children of this folder that are also folders. - * - * @return DataList - */ - public function ChildFolders() - { - return Folder::get()->filter('ParentID', $this->ID); - } - - /** - * Get the number of children of this folder that are also folders. - * - * @return int - */ - public function numChildFolders() - { - return $this->ChildFolders()->count(); - } - /** - * @return string - */ - public function CMSTreeClasses() - { - $classes = sprintf('class-%s', $this->class); - - if (!$this->canDelete()) { - $classes .= " nodelete"; - } - - if (!$this->canEdit()) { - $classes .= " disabled"; - } - - $classes .= $this->markingClasses('numChildFolders'); - - return $classes; - } - - /** - * @return string - */ - public function getTreeTitle() - { - return sprintf( - "%s", - Convert::raw2att(preg_replace('~\R~u', ' ', $this->Title)) - ); - } - - public function getFilename() - { - return parent::generateFilename() . '/'; - } - - /** - * Folders do not have public URLs - * - * @param bool $grant - * @return null|string - */ - public function getURL($grant = true) - { - return null; - } - - /** - * Folders do not have public URLs - * - * @return string - */ - public function getAbsoluteURL() - { - return null; - } - - public function onAfterWrite() - { - parent::onAfterWrite(); - - // No publishing UX for folders, so just cascade changes live - if (Versioned::get_stage() === Versioned::DRAFT) { - $this->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); - } - - // Update draft version of all child records - $this->updateChildFilesystem(); - } - - public function onAfterDelete() - { - parent::onAfterDelete(); - - // Cascade deletions to live - if (Versioned::get_stage() === Versioned::DRAFT) { - $this->deleteFromStage(Versioned::LIVE); - } - } - - public function updateFilesystem() - { - // No filesystem changes to update - } - - /** - * If a write is skipped due to no changes, ensure that nested records still get asked to update - */ - public function onAfterSkippedWrite() - { - $this->updateChildFilesystem(); - } - - /** - * Update filesystem of all children - */ - public function updateChildFilesystem() - { - // Don't synchronise on live (rely on publishing instead) - if (Versioned::get_stage() === Versioned::LIVE) { - return; - } - - $this->flushCache(); - // Writing this record should trigger a write (and potential updateFilesystem) on each child - foreach ($this->AllChildren() as $child) { - $child->write(); - } - } - - public function StripThumbnail() - { - return null; - } - - public function validate() - { - $result = ValidationResult::create(); - $this->extend('validate', $result); - return $result; - } -} diff --git a/src/Assets/GDBackend.php b/src/Assets/GDBackend.php deleted file mode 100644 index a530af2b4..000000000 --- a/src/Assets/GDBackend.php +++ /dev/null @@ -1,767 +0,0 @@ -cache = Injector::inst()->get(CacheInterface::class . '.GDBackend_Manipulations'); - - if ($assetContainer) { - $this->loadFromContainer($assetContainer); - } - } - - public function __destruct() - { - if ($resource = $this->getImageResource()) { - imagedestroy($resource); - } - } - - public function loadFrom($path) - { - // If we're working with image resampling, things could take a while. Bump up the time-limit - increase_time_limit_to(300); - $this->resetResource(); - - // Skip if path is unavailable - if (!file_exists($path)) { - return; - } - $mtime = filemtime($path); - - // Skip if load failed before - if ($this->failedResample($path, $mtime)) { - return; - } - - // We use getimagesize instead of extension checking, because sometimes extensions are wrong. - $meta = getimagesize($path); - if ($meta === false || !$this->checkAvailableMemory($meta)) { - $this->markFailed($path, $mtime); - return; - } - - $gd = null; - switch ($meta[2]) { - case 1: - if (function_exists('imagecreatefromgif')) { - $gd = imagecreatefromgif($path); - } - break; - case 2: - if (function_exists('imagecreatefromjpeg')) { - $gd = imagecreatefromjpeg($path); - } - break; - case 3: - if (function_exists('imagecreatefrompng')) { - $gd = imagecreatefrompng($path); - if ($gd) { - imagesavealpha($gd, true); // save alphablending setting (important) - } - } - break; - } - - // image failed - if ($gd === false) { - $this->markFailed($path, $mtime); - return; - } - - // Save - $this->setImageResource($gd); - } - - public function loadFromContainer(AssetContainer $assetContainer) - { - // If we're working with image resampling, things could take a while. Bump up the time-limit - increase_time_limit_to(300); - $this->resetResource(); - - // Skip non-existant files - if (!$assetContainer->exists()) { - return; - } - - // Skip if failed before, or image is too large - $filename = $assetContainer->getFilename(); - $hash = $assetContainer->getHash(); - $variant = $assetContainer->getVariant(); - if ($this->failedResample($filename, $hash, $variant)) { - return; - } - - $content = $assetContainer->getString(); - - // We use getimagesizefromstring instead of extension checking, because sometimes extensions are wrong. - $meta = getimagesizefromstring($content); - if ($meta === false || !$this->checkAvailableMemory($meta)) { - $this->markFailed($filename, $hash, $variant); - return; - } - - // Mark as potentially failed prior to creation, resetting this on success - $image = imagecreatefromstring($content); - if ($image === false) { - $this->markFailed($filename, $hash, $variant); - return; - } - - imagealphablending($image, false); - imagesavealpha($image, true); // save alphablending setting (important) - $this->setImageResource($image); - } - - /** - * Clear GD resource - */ - protected function resetResource() - { - // Set defaults and clear resource - $this->setImageResource(null); - $this->quality = $this->config()->default_quality; - $this->interlace = $this->config()->image_interlace; - } - - /** - * Assign or clear GD resource - * - * @param resource|null $resource - */ - public function setImageResource($resource) - { - $this->gd = $resource; - $this->width = $resource ? imagesx($resource) : 0; - $this->height = $resource ? imagesy($resource) : 0; - } - - /** - * Get the currently assigned GD resource - * - * @return resource - */ - public function getImageResource() - { - return $this->gd; - } - - /** - * Check if this image has previously crashed GD when attempting to open it - if it's opened - * successfully, the manipulation's cache key is removed. - * - * @param string $arg,... Any number of args that identify this image - * @return bool True if failed - */ - public function failedResample($arg = null) - { - $key = sha1(implode('|', func_get_args())); - return (bool)$this->cache->get($key); - } - - /** - * Check if we've got enough memory available for resampling this image. This check is rough, - * so it will not catch all images that are too large - it also won't work accurately on large, - * animated GIFs as bits per pixel can't be calculated for an animated GIF with a global color - * table. - * - * @param array $imageInfo Value from getimagesize() or getimagesizefromstring() - * @return boolean - */ - protected function checkAvailableMemory($imageInfo) - { - $limit = translate_memstring(ini_get('memory_limit')); - if ($limit < 0) { - return true; // memory_limit == -1 - } - - // bits per channel (rounded up, default to 1) - $bits = isset($imageInfo['bits']) ? ($imageInfo['bits'] + 7) / 8 : 1; - - // channels (default 4 rgba) - $channels = isset($imageInfo['channels']) ? $imageInfo['channels'] : 4; - $bytesPerPixel = $bits * $channels; - - // width * height * bytes per pixel - $memoryRequired = $imageInfo[0] * $imageInfo[1] * $bytesPerPixel; - - return $memoryRequired + memory_get_usage() < $limit; - } - - /** - * Mark a file as failed - * - * @param string $arg,... Any number of args that identify this image - */ - protected function markFailed($arg = null) - { - $key = sha1(implode('|', func_get_args())); - $this->cache->set($key, '1'); - } - - /** - * Mark a file as succeeded - * - * @param string $arg,... Any number of args that identify this image - */ - protected function markSucceeded($arg = null) - { - $key = sha1(implode('|', func_get_args())); - $this->cache->set($key, '0'); - } - - - public function setQuality($quality) - { - $this->quality = $quality; - } - - public function croppedResize($width, $height) - { - if (!$this->gd) { - return null; - } - - $width = round($width); - $height = round($height); - - // Check that a resize is actually necessary. - if ($width == $this->width && $height == $this->height) { - return $this; - } - - $newGD = imagecreatetruecolor($width, $height); - - // Preserves transparency between images - imagealphablending($newGD, false); - imagesavealpha($newGD, true); - - $destAR = $width / $height; - if ($this->width > 0 && $this->height > 0) { - // We can't divide by zero theres something wrong. - - $srcAR = $this->width / $this->height; - - // Destination narrower than the source - if ($destAR < $srcAR) { - $srcY = 0; - $srcHeight = $this->height; - - $srcWidth = round($this->height * $destAR); - $srcX = round(($this->width - $srcWidth) / 2); - - // Destination shorter than the source - } else { - $srcX = 0; - $srcWidth = $this->width; - - $srcHeight = round($this->width / $destAR); - $srcY = round(($this->height - $srcHeight) / 2); - } - - imagecopyresampled($newGD, $this->gd, 0, 0, $srcX, $srcY, $width, $height, $srcWidth, $srcHeight); - } - $output = clone $this; - $output->setImageResource($newGD); - return $output; - } - - /** - * Resizes the image to fit within the given region. - * Behaves similarly to paddedResize but without the padding. - * @todo This method isn't very efficent - * - * @param int $width - * @param int $height - * @return static - */ - public function fittedResize($width, $height) - { - $gd = $this->resizeByHeight($height); - if ($gd->width > $width) { - $gd = $gd->resizeByWidth($width); - } - return $gd; - } - - /** - * @param int $width - * @param int $height - * @return static - */ - public function resize($width, $height) - { - if (!$this->gd) { - return null; - } - - if ($width < 0 || $height < 0) { - throw new InvalidArgumentException("Image resizing dimensions cannot be negative"); - } - if (!$width && !$height) { - throw new InvalidArgumentException("No dimensions given when resizing image"); - } - if (!$width) { - throw new InvalidArgumentException("Width not given when resizing image"); - } - if (!$height) { - throw new InvalidArgumentException("Height not given when resizing image"); - } - - //use whole numbers, ensuring that size is at least 1x1 - $width = max(1, round($width)); - $height = max(1, round($height)); - - // Check that a resize is actually necessary. - if ($width == $this->width && $height == $this->height) { - return $this; - } - - - $newGD = imagecreatetruecolor($width, $height); - - // Preserves transparency between images - imagealphablending($newGD, false); - imagesavealpha($newGD, true); - - imagecopyresampled($newGD, $this->gd, 0, 0, 0, 0, $width, $height, $this->width, $this->height); - - $output = clone $this; - $output->setImageResource($newGD); - return $output; - } - - /** - * Rotates image by given angle. - * - * @param float $angle Angle in degrees - * @return static - */ - public function rotate($angle) - { - if (!$this->gd) { - return null; - } - - if (function_exists("imagerotate")) { - $newGD = imagerotate($this->gd, $angle, 0); - } else { - //imagerotate is not included in PHP included in Ubuntu - $newGD = $this->rotatePixelByPixel($angle); - } - $output = clone $this; - $output->setImageResource($newGD); - return $output; - } - - /** - * Rotates image by given angle. It's slow because makes it pixel by pixel rather than - * using built-in function. Used when imagerotate function is not available(i.e. Ubuntu) - * - * @param float $angle Angle in degrees - * @return static - */ - public function rotatePixelByPixel($angle) - { - if (!$this->gd) { - return null; - } - $sourceWidth = imagesx($this->gd); - $sourceHeight = imagesy($this->gd); - if ($angle == 180) { - $destWidth = $sourceWidth; - $destHeight = $sourceHeight; - } else { - $destWidth = $sourceHeight; - $destHeight = $sourceWidth; - } - $rotate=imagecreatetruecolor($destWidth, $destHeight); - imagealphablending($rotate, false); - for ($x = 0; $x < ($sourceWidth); $x++) { - for ($y = 0; $y < ($sourceHeight); $y++) { - $color = imagecolorat($this->gd, $x, $y); - switch ($angle) { - case 90: - imagesetpixel($rotate, $y, $destHeight - $x - 1, $color); - break; - case 180: - imagesetpixel($rotate, $destWidth - $x - 1, $destHeight - $y - 1, $color); - break; - case 270: - imagesetpixel($rotate, $destWidth - $y - 1, $x, $color); - break; - default: - $rotate = $this->gd; - }; - } - } - return $rotate; - } - - - /** - * Crop's part of image. - * - * @param int $top y position of left upper corner of crop rectangle - * @param int $left x position of left upper corner of crop rectangle - * @param int $width rectangle width - * @param int $height rectangle height - * @return static - */ - public function crop($top, $left, $width, $height) - { - if (!$this->gd) { - return null; - } - - $newGD = imagecreatetruecolor($width, $height); - - // Preserve alpha channel between images - imagealphablending($newGD, false); - imagesavealpha($newGD, true); - - imagecopyresampled($newGD, $this->gd, 0, 0, $left, $top, $width, $height, $width, $height); - - $output = clone $this; - $output->setImageResource($newGD); - return $output; - } - - /** - * Width of image. - * - * @return int - */ - public function getWidth() - { - return $this->width; - } - - /** - * Height of image. - * - * @return int - */ - public function getHeight() - { - return $this->height; - } - - public function resizeByWidth($width) - { - $heightScale = $width / $this->width; - return $this->resize($width, $heightScale * $this->height); - } - - /** - * @param int $height - * @return static - */ - public function resizeByHeight($height) - { - $scale = $height / $this->height; - return $this->resize($scale * $this->width, $height); - } - - public function resizeRatio($maxWidth, $maxHeight, $useAsMinimum = false) - { - $widthRatio = $maxWidth / $this->width; - $heightRatio = $maxHeight / $this->height; - - if ($widthRatio < $heightRatio) { - return $useAsMinimum - ? $this->resizeByHeight($maxHeight) - : $this->resizeByWidth($maxWidth); - } else { - return $useAsMinimum - ? $this->resizeByWidth($maxWidth) - : $this->resizeByHeight($maxHeight); - } - } - - public function paddedResize($width, $height, $backgroundColor = "FFFFFF") - { - if (!$this->gd) { - return null; - } - $width = round($width); - $height = round($height); - - // Check that a resize is actually necessary. - if ($width == $this->width && $height == $this->height) { - return $this; - } - - $newGD = imagecreatetruecolor($width, $height); - - // Preserves transparency between images - imagealphablending($newGD, false); - imagesavealpha($newGD, true); - - $bg = $this->colourWeb2GD($newGD, $backgroundColor); - imagefilledrectangle($newGD, 0, 0, $width, $height, $bg); - - $destAR = $width / $height; - if ($this->width > 0 && $this->height > 0) { - // We can't divide by zero theres something wrong. - - $srcAR = $this->width / $this->height; - - // Destination narrower than the source - if ($destAR > $srcAR) { - $destY = 0; - $destHeight = $height; - - $destWidth = round($height * $srcAR); - $destX = round(($width - $destWidth) / 2); - - // Destination shorter than the source - } else { - $destX = 0; - $destWidth = $width; - - $destHeight = round($width / $srcAR); - $destY = round(($height - $destHeight) / 2); - } - - imagecopyresampled( - $newGD, - $this->gd, - $destX, - $destY, - 0, - 0, - $destWidth, - $destHeight, - $this->width, - $this->height - ); - } - $output = clone $this; - $output->setImageResource($newGD); - return $output; - } - - /** - * Make the image greyscale. - * Default color weights are based on standard BT.601 (those used in PAL, NTSC and many software packages, also see - * https://en.wikipedia.org/wiki/Grayscale#Luma_coding_in_video_systems ) - * - * @param int $R red weight, defaults to 299 - * @param int $G green weight, defaults to 587 - * @param int $B blue weight, defaults to 114 - * @param int $brightness brightness in percentage, defaults to 100 - * @return GDBackend - */ - public function greyscale($R = 299, $G = 587, $B = 114, $brightness = 100) - { - if (!$this->gd) { - return null; - } - - $width = $this->width; - $height = $this->height; - $newGD = imagecreatetruecolor($this->width, $this->height); - - // Preserves transparency between images - imagealphablending($newGD, false); - imagesavealpha($newGD, true); - - $rt = $R + $G + $B; - // if $rt is 0, bad parameters are provided, so result will be a black image - $rr = $rt ? $R/$rt : 0; - $gr = $rt ? $G/$rt : 0; - $br = $rt ? $B/$rt : 0; - // iterate over all pixels and make them grey - for ($dy = 0; $dy < $height; $dy++) { - for ($dx = 0; $dx < $width; $dx++) { - $pxrgb = imagecolorat($this->gd, $dx, $dy); - $heightgb = imagecolorsforindex($this->gd, $pxrgb); - $newcol = ($rr*$heightgb['red']) + ($br*$heightgb['blue']) + ($gr*$heightgb['green']); - $newcol = min(255, $newcol*$brightness/100); - $setcol = imagecolorallocatealpha($newGD, $newcol, $newcol, $newcol, $heightgb['alpha']); - imagesetpixel($newGD, $dx, $dy, $setcol); - } - } - - $output = clone $this; - $output->setImageResource($newGD); - return $output; - } - - public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $variant = null, $config = array()) - { - // Write to temporary file, taking care to maintain the extension - $path = tempnam(sys_get_temp_dir(), 'gd'); - if ($extension = pathinfo($filename, PATHINFO_EXTENSION)) { - $path .= "." . $extension; - } - - $writeSuccess = $this->writeTo($path); - if (!$writeSuccess) { - return null; - } - - $result = $assetStore->setFromLocalFile($path, $filename, $hash, $variant, $config); - unlink($path); - - return $result; - } - - /** - * @param string $filename - * @return boolean - */ - public function writeTo($filename) - { - if (!$filename) { - return false; - } - - // The GD resource might not exist if the image is too large to be processed, see checkAvailableMemory(). - if (!$this->gd) { - return false; - } - - // Get current image data - if (file_exists($filename)) { - list($width, $height, $type, $attr) = getimagesize($filename); - unlink($filename); - } else { - Filesystem::makeFolder(dirname($filename)); - } - - // If image type isn't known, guess from extension - $ext = strtolower(substr($filename, strrpos($filename, '.')+1)); - if (empty($type)) { - switch ($ext) { - case "gif": - $type = IMAGETYPE_GIF; - break; - case "jpeg": - case "jpg": - case "jpe": - $type = IMAGETYPE_JPEG; - break; - default: - $type = IMAGETYPE_PNG; - break; - } - } - - // If $this->interlace != 0, the output image will be interlaced. - imageinterlace($this->gd, $this->interlace); - - // if the extension does not exist, the file will not be created! - switch ($type) { - case IMAGETYPE_GIF: - imagegif($this->gd, $filename); - break; - case IMAGETYPE_JPEG: - imagejpeg($this->gd, $filename, $this->quality); - break; - - // case 3, and everything else - default: - // Save them as 8-bit images - // imagetruecolortopalette($this->gd, false, 256); - imagepng($this->gd, $filename); - break; - } - - if (!file_exists($filename)) { - return false; - } - - @chmod($filename, 0664); - - return true; - } - - /** - * Helper function to allocate a colour to an image - * - * @param resource $image - * @param string $webColor - * @return int - */ - protected function colourWeb2GD($image, $webColor) - { - if (substr($webColor, 0, 1) == "#") { - $webColor = substr($webColor, 1); - } - $r = hexdec(substr($webColor, 0, 2)); - $g = hexdec(substr($webColor, 2, 2)); - $b = hexdec(substr($webColor, 4, 2)); - - return imagecolorallocate($image, $r, $g, $b); - } - - public static function flush() - { - $cache = Injector::inst()->get(CacheInterface::class . '.GDBackend_Manipulations'); - $cache->clear(); - } -} diff --git a/src/Assets/Image.php b/src/Assets/Image.php deleted file mode 100644 index e11be4ea5..000000000 --- a/src/Assets/Image.php +++ /dev/null @@ -1,255 +0,0 @@ -File->setAllowedCategories('image/supported'); - } - - public function getCMSFields() - { - $path = '/' . dirname($this->getFilename()); - - $previewLink = Convert::raw2att($this->PreviewLink()); - $image = ""; - - $link = $this->Link(); - - $statusTitle = $this->getStatusTitle(); - $statusFlag = "{$statusTitle}"; - - $content = Tab::create( - 'Main', - HeaderField::create('TitleHeader', $this->Title, 1) - ->addExtraClass('editor__heading'), - LiteralField::create("ImageFull", $image) - ->addExtraClass('editor__file-preview'), - TabSet::create( - 'Editor', - Tab::create( - 'Details', - TextField::create("Title", $this->fieldLabel('Title')), - TextField::create("Name", $this->fieldLabel('Filename')), - ReadonlyField::create( - "Path", - _t('AssetTableField.PATH', 'Path'), - (($path !== '/.') ? $path : '') . '/' - ), - HTMLReadonlyField::create( - 'ClickableURL', - _t('AssetTableField.URL', 'URL'), - sprintf( - '%s', - 'font-icon-link btn--icon-large form-control-static__icon', - $link, - $link - ) - ) - ), - Tab::create( - 'Usage', - DatetimeField::create( - "Created", - _t('AssetTableField.CREATED', 'First uploaded') - )->setReadonly(true), - DatetimeField::create( - "LastEdited", - _t('AssetTableField.LASTEDIT', 'Last changed') - )->setReadonly(true) - ) - ), - HiddenField::create('ID', $this->ID) - ); - - if ($dimensions = $this->getDimensions()) { - $content->insertAfter( - 'TitleHeader', - LiteralField::create( - "DisplaySize", - sprintf( - '

    %spx, %s %s
    ', - $dimensions, - $this->getSize(), - $statusFlag - ) - ) - ); - } else { - $content->insertAfter( - 'TitleHeader', - LiteralField::create('StatusFlag', $statusFlag) - ); - } - - $fields = FieldList::create(TabSet::create('Root', $content)); - - $this->extend('updateCMSFields', $fields); - - return $fields; - } - - public function getIsImage() - { - return true; - } - - /** - * Replace"[image id=n]" shortcode with an image reference. - * Permission checks will be enforced by the file routing itself. - * - * @param array $args Arguments passed to the parser - * @param string $content Raw shortcode - * @param ShortcodeParser $parser Parser - * @param string $shortcode Name of shortcode used to register this handler - * @param array $extra Extra arguments - * @return string Result of the handled shortcode - */ - public static function handle_shortcode($args, $content, $parser, $shortcode, $extra = array()) - { - // Find appropriate record, with fallback for error handlers - $record = static::find_shortcode_record($args, $errorCode); - if ($errorCode) { - $record = static::find_error_record($errorCode); - } - if (!$record) { - return null; // There were no suitable matches at all. - } - - // Check if a resize is required - $src = $record->Link(); - if ($record instanceof Image) { - $width = isset($args['width']) ? $args['width'] : null; - $height = isset($args['height']) ? $args['height'] : null; - $hasCustomDimensions = ($width && $height); - if ($hasCustomDimensions && (($width != $record->getWidth()) || ($height != $record->getHeight()))) { - $resized = $record->ResizedImage($width, $height); - // Make sure that the resized image actually returns an image - if ($resized) { - $src = $resized->getURL(); - } - } - } - - // Build the HTML tag - $attrs = array_merge( - // Set overrideable defaults - ['src' => '', 'alt' => $record->Title], - // Use all other shortcode arguments - $args, - // But enforce some values - ['id' => '', 'src' => $src] - ); - - // Clean out any empty attributes - $attrs = array_filter($attrs, function ($v) { - return (bool)$v; - }); - - // Condense to HTML attribute string - $attrsStr = join(' ', array_map(function ($name) use ($attrs) { - return Convert::raw2att($name) . '="' . Convert::raw2att($attrs[$name]) . '"'; - }, array_keys($attrs))); - - return ''; - } - - /** - * Regenerates "[image id=n]" shortcode with new src attribute prior to being edited within the CMS. - * - * @param array $args Arguments passed to the parser - * @param string $content Raw shortcode - * @param ShortcodeParser $parser Parser - * @param string $shortcode Name of shortcode used to register this handler - * @param array $extra Extra arguments - * @return string Result of the handled shortcode - */ - public static function regenerate_shortcode($args, $content, $parser, $shortcode, $extra = array()) - { - // Check if there is a suitable record - $record = static::find_shortcode_record($args); - if ($record) { - $args['src'] = $record->getURL(); - } - - // Rebuild shortcode - $parts = array(); - foreach ($args as $name => $value) { - $htmlValue = Convert::raw2att($value ?: $name); - $parts[] = sprintf('%s="%s"', $name, $htmlValue); - } - return sprintf("[%s %s]", $shortcode, implode(' ', $parts)); - } - - /** - * Helper method to regenerate all shortcode links. - * - * @param string $value HTML value - * @return string value with links resampled - */ - public static function regenerate_html_links($value) - { - // Create a shortcode generator which only regenerates links - $regenerator = ShortcodeParser::get('regenerator'); - return $regenerator->parse($value); - } - - public function PreviewLink($action = null) - { - // Since AbsoluteLink can whitelist protected assets, - // do permission check first - if (!$this->canView()) { - return false; - } - - // Size to width / height - $width = (int)$this->config()->get('asset_preview_width'); - $height = (int)$this->config()->get('asset_preview_height'); - $resized = $this->FitMax($width, $height); - if ($resized && $resized->exists()) { - $link = $resized->getAbsoluteURL(); - } else { - $link = $this->getIcon(); - } - $this->extend('updatePreviewLink', $link, $action); - return $link; - } -} diff --git a/src/Assets/ImageManipulation.php b/src/Assets/ImageManipulation.php deleted file mode 100644 index 104a86be7..000000000 --- a/src/Assets/ImageManipulation.php +++ /dev/null @@ -1,828 +0,0 @@ -isSize($width, $height)) { - return $this; - } - - $variant = $this->variantName(__FUNCTION__, $width, $height, $backgroundColor); - return $this->manipulateImage( - $variant, - function (Image_Backend $backend) use ($width, $height, $backgroundColor) { - return $backend->paddedResize($width, $height, $backgroundColor); - } - ); - } - - /** - * Forces the image to be resampled, if possible - * - * @return AssetContainer - */ - public function Resampled() - { - // If image is already resampled, return self reference - $variant = $this->getVariant(); - if ($variant) { - return $this; - } - - // Resample, but fallback to original object - $result = $this->manipulateImage(__FUNCTION__, function (Image_Backend $backend) { - return $backend; - }); - if ($result) { - return $result; - } - return $this; - } - - /** - * Update the url to point to a resampled version if forcing - * - * @param string $url - */ - public function updateURL(&$url) - { - // Skip if resampling is off, or is already resampled, or is not an image - if (!Config::inst()->get(get_class($this), 'force_resample') || $this->getVariant() || !$this->getIsImage()) { - return; - } - - // Attempt to resample - $resampled = $this->Resampled(); - if (!$resampled) { - return; - } - - // Only update if resampled file is a smaller file size - if ($resampled->getAbsoluteSize() < $this->getAbsoluteSize()) { - $url = $resampled->getURL(); - } - } - - - /** - * Generate a resized copy of this image with the given width & height. - * This can be used in templates with $ResizedImage but should be avoided, - * as it's the only image manipulation function which can skew an image. - * - * @param integer $width Width to resize to - * @param integer $height Height to resize to - * @return AssetContainer - */ - public function ResizedImage($width, $height) - { - if ($this->isSize($width, $height)) { - return $this; - } - - $variant = $this->variantName(__FUNCTION__, $width, $height); - return $this->manipulateImage($variant, function (Image_Backend $backend) use ($width, $height) { - return $backend->resize($width, $height); - }); - } - - /** - * Scale image proportionally to fit within the specified bounds - * - * @param integer $width The width to size within - * @param integer $height The height to size within - * @return AssetContainer - */ - public function Fit($width, $height) - { - // Prevent divide by zero on missing/blank file - if (!$this->getWidth() || !$this->getHeight()) { - return null; - } - - // Check if image is already sized to the correct dimension - $widthRatio = $width / $this->getWidth(); - $heightRatio = $height / $this->getHeight(); - - if ($widthRatio < $heightRatio) { - // Target is higher aspect ratio than image, so check width - if ($this->isWidth($width)) { - return $this; - } - } else { - // Target is wider or same aspect ratio as image, so check height - if ($this->isHeight($height)) { - return $this; - } - } - - // Item must be regenerated - $variant = $this->variantName(__FUNCTION__, $width, $height); - return $this->manipulateImage($variant, function (Image_Backend $backend) use ($width, $height) { - return $backend->resizeRatio($width, $height); - }); - } - - /** - * Proportionally scale down this image if it is wider or taller than the specified dimensions. - * Similar to Fit but without up-sampling. Use in templates with $FitMax. - * - * @uses ScalingManipulation::Fit() - * @param integer $width The maximum width of the output image - * @param integer $height The maximum height of the output image - * @return AssetContainer - */ - public function FitMax($width, $height) - { - return $this->getWidth() > $width || $this->getHeight() > $height - ? $this->Fit($width, $height) - : $this; - } - - - /** - * Scale image proportionally by width. Use in templates with $ScaleWidth. - * - * @param integer $width The width to set - * @return AssetContainer - */ - public function ScaleWidth($width) - { - if ($this->isWidth($width)) { - return $this; - } - - $variant = $this->variantName(__FUNCTION__, $width); - return $this->manipulateImage($variant, function (Image_Backend $backend) use ($width) { - return $backend->resizeByWidth($width); - }); - } - - /** - * Proportionally scale down this image if it is wider than the specified width. - * Similar to ScaleWidth but without up-sampling. Use in templates with $ScaleMaxWidth. - * - * @uses ScalingManipulation::ScaleWidth() - * @param integer $width The maximum width of the output image - * @return AssetContainer - */ - public function ScaleMaxWidth($width) - { - return $this->getWidth() > $width - ? $this->ScaleWidth($width) - : $this; - } - - /** - * Scale image proportionally by height. Use in templates with $ScaleHeight. - * - * @param int $height The height to set - * @return AssetContainer - */ - public function ScaleHeight($height) - { - if ($this->isHeight($height)) { - return $this; - } - - $variant = $this->variantName(__FUNCTION__, $height); - return $this->manipulateImage($variant, function (Image_Backend $backend) use ($height) { - return $backend->resizeByHeight($height); - }); - } - - /** - * Proportionally scale down this image if it is taller than the specified height. - * Similar to ScaleHeight but without up-sampling. Use in templates with $ScaleMaxHeight. - * - * @uses ScalingManipulation::ScaleHeight() - * @param integer $height The maximum height of the output image - * @return AssetContainer - */ - public function ScaleMaxHeight($height) - { - return $this->getHeight() > $height - ? $this->ScaleHeight($height) - : $this; - } - - - /** - * Crop image on X axis if it exceeds specified width. Retain height. - * Use in templates with $CropWidth. Example: $Image.ScaleHeight(100).$CropWidth(100) - * - * @uses CropManipulation::Fill() - * @param integer $width The maximum width of the output image - * @return AssetContainer - */ - public function CropWidth($width) - { - return $this->getWidth() > $width - ? $this->Fill($width, $this->getHeight()) - : $this; - } - - /** - * Crop image on Y axis if it exceeds specified height. Retain width. - * Use in templates with $CropHeight. Example: $Image.ScaleWidth(100).CropHeight(100) - * - * @uses CropManipulation::Fill() - * @param integer $height The maximum height of the output image - * @return AssetContainer - */ - public function CropHeight($height) - { - return $this->getHeight() > $height - ? $this->Fill($this->getWidth(), $height) - : $this; - } - - /** - * Crop this image to the aspect ratio defined by the specified width and height, - * then scale down the image to those dimensions if it exceeds them. - * Similar to Fill but without up-sampling. Use in templates with $FillMax. - * - * @uses ImageManipulation::Fill() - * @param integer $width The relative (used to determine aspect ratio) and maximum width of the output image - * @param integer $height The relative (used to determine aspect ratio) and maximum height of the output image - * @return AssetContainer - */ - public function FillMax($width, $height) - { - // Prevent divide by zero on missing/blank file - if (!$this->getWidth() || !$this->getHeight()) { - return null; - } - - // Is the image already the correct size? - if ($this->isSize($width, $height)) { - return $this; - } - - // If not, make sure the image isn't upsampled - $imageRatio = $this->getWidth() / $this->getHeight(); - $cropRatio = $width / $height; - // If cropping on the x axis compare heights - if ($cropRatio < $imageRatio && $this->getHeight() < $height) { - return $this->Fill($this->getHeight() * $cropRatio, $this->getHeight()); - } - - // Otherwise we're cropping on the y axis (or not cropping at all) so compare widths - if ($this->getWidth() < $width) { - return $this->Fill($this->getWidth(), $this->getWidth() / $cropRatio); - } - - return $this->Fill($width, $height); - } - - /** - * Resize and crop image to fill specified dimensions. - * Use in templates with $Fill - * - * @param integer $width Width to crop to - * @param integer $height Height to crop to - * @return AssetContainer - */ - public function Fill($width, $height) - { - if ($this->isSize($width, $height)) { - return $this; - } - - // Resize - $variant = $this->variantName(__FUNCTION__, $width, $height); - return $this->manipulateImage($variant, function (Image_Backend $backend) use ($width, $height) { - return $backend->croppedResize($width, $height); - }); - } - - /** - * Default CMS thumbnail - * - * @return DBFile|DBHTMLText Either a resized thumbnail, or html for a thumbnail icon - */ - public function CMSThumbnail() - { - $width = (int)Config::inst()->get(__CLASS__, 'cms_thumbnail_width'); - $height = (int)Config::inst()->get(__CLASS__, 'cms_thumbnail_height'); - return $this->ThumbnailIcon($width, $height); - } - - /** - * Generates a thumbnail for use in the gridfield view - * - * @return AssetContainer|DBHTMLText Either a resized thumbnail, or html for a thumbnail icon - */ - public function StripThumbnail() - { - $width = (int)Config::inst()->get(__CLASS__, 'strip_thumbnail_width'); - $height = (int)Config::inst()->get(__CLASS__, 'strip_thumbnail_height'); - return $this->ThumbnailIcon($width, $height); - } - - /** - * Get preview for this file - * - * @return AssetContainer|DBHTMLText Either a resized thumbnail, or html for a thumbnail icon - */ - public function PreviewThumbnail() - { - $width = (int)Config::inst()->get(__CLASS__, 'asset_preview_width'); - return $this->ScaleMaxWidth($width) ?: $this->IconTag(); - } - - /** - * Default thumbnail generation for Images - * - * @param int $width - * @param int $height - * @return AssetContainer - */ - public function Thumbnail($width, $height) - { - return $this->Pad($width, $height); - } - - /** - * Thubnail generation for all file types. - * - * Resizes images, but returns an icon tag if this is not a resizable image - * - * @param int $width - * @param int $height - * @return AssetContainer|DBHTMLText - */ - public function ThumbnailIcon($width, $height) - { - return $this->Thumbnail($width, $height) ?: $this->IconTag(); - } - - /** - * Get HTML for img containing the icon for this file - * - * @return DBHTMLText - */ - public function IconTag() - { - return DBField::create_field( - 'HTMLFragment', - '' - ); - } - - /** - * Get URL to thumbnail of the given size. - * - * May fallback to default icon - * - * @param int $width - * @param int $height - * @return string - */ - public function ThumbnailURL($width, $height) - { - $thumbnail = $this->Thumbnail($width, $height); - if ($thumbnail) { - return $thumbnail->getURL(); - } - return $this->getIcon(); - } - - /** - * Return the relative URL of an icon for the file type, - * based on the {@link appCategory()} value. - * Images are searched for in "framework/images/app_icons/". - * - * @return string URL to icon - */ - public function getIcon() - { - $filename = $this->getFilename(); - $ext = pathinfo($filename, PATHINFO_EXTENSION); - return File::get_icon_for_extension($ext); - } - - /** - * Get Image_Backend instance for this image - * - * @return Image_Backend - */ - public function getImageBackend() - { - if (!$this->getIsImage()) { - return null; - } - - // Create backend for this object - /** @skipUpgrade */ - return Injector::inst()->createWithArgs('Image_Backend', array($this)); - } - - /** - * Get the dimensions of this Image. - * - * @param string $dim One of the following: - * - "string": return the dimensions in string form - * - "array": it'll return the raw result - * - 0: return the height - * - 1: return the width - * @return string|int|array|null - */ - public function getDimensions($dim = "string") - { - if (!$this->getIsImage()) { - return null; - } - - $content = $this->getString(); - if (!$content) { - return null; - } - - // Get raw content - $size = getimagesizefromstring($content); - if ($size === false) { - return null; - } - - if ($dim === 'array') { - return $size; - } - - // Get single dimension - if (is_numeric($dim)) { - return $size[$dim]; - } - - return "$size[0]x$size[1]"; - } - - /** - * Get the width of this image. - * - * @return int - */ - public function getWidth() - { - return $this->getDimensions(0); - } - - /** - * Get the height of this image. - * - * @return int - */ - public function getHeight() - { - return $this->getDimensions(1); - } - - /** - * Get the orientation of this image. - * - * @return int ORIENTATION_SQUARE | ORIENTATION_PORTRAIT | ORIENTATION_LANDSCAPE - */ - public function getOrientation() - { - $width = $this->getWidth(); - $height = $this->getHeight(); - if ($width > $height) { - return Image_Backend::ORIENTATION_LANDSCAPE; - } elseif ($height > $width) { - return Image_Backend::ORIENTATION_PORTRAIT; - } else { - return Image_Backend::ORIENTATION_SQUARE; - } - } - - /** - * Determine if this image is of the specified size - * - * @param integer $width Width to check - * @param integer $height Height to check - * @return boolean - */ - public function isSize($width, $height) - { - return $this->isWidth($width) && $this->isHeight($height); - } - - /** - * Determine if this image is of the specified width - * - * @param integer $width Width to check - * @return boolean - */ - public function isWidth($width) - { - if (empty($width) || !is_numeric($width)) { - throw new InvalidArgumentException("Invalid value for width"); - } - return $this->getWidth() == $width; - } - - /** - * Determine if this image is of the specified width - * - * @param integer $height Height to check - * @return boolean - */ - public function isHeight($height) - { - if (empty($height) || !is_numeric($height)) { - throw new InvalidArgumentException("Invalid value for height"); - } - return $this->getHeight() == $height; - } - - /** - * Wrapper for manipulate that passes in and stores Image_Backend objects instead of tuples - * - * @param string $variant - * @param callable $callback Callback which takes an Image_Backend object, and returns an Image_Backend result - * @return DBFile The manipulated file - */ - public function manipulateImage($variant, $callback) - { - return $this->manipulate( - $variant, - function (AssetStore $store, $filename, $hash, $variant) use ($callback) { - /** @var Image_Backend $backend */ - $backend = $this->getImageBackend(); - - // If backend isn't available - if (!$backend || !$backend->getImageResource()) { - return null; - } - $backend = $callback($backend); - if (!$backend) { - return null; - } - - $return = $backend->writeToStore( - $store, - $filename, - $hash, - $variant, - array('conflict' => AssetStore::CONFLICT_USE_EXISTING) - ); - - // Enforce garbage collection on $backend, avoid increasing memory use on each manipulation - // by holding on to the underlying GD image resource. - // Even though it's a local variable with no other references, - // PHP holds on to it for the entire lifecycle of the script, - // which is potentially related to passing it into the $callback closure. - gc_collect_cycles(); - - return $return; - } - ); - } - - /** - * Generate a new DBFile instance using the given callback if it hasn't been created yet, or - * return the existing one if it has. - * - * @param string $variant name of the variant to create - * @param callable $callback Callback which should return a new tuple as an array. - * This callback will be passed the backend, filename, hash, and variant - * This will not be called if the file does not - * need to be created. - * @return DBFile The manipulated file - */ - public function manipulate($variant, $callback) - { - // Verify this manipulation is applicable to this instance - if (!$this->exists()) { - return null; - } - - // Build output tuple - $filename = $this->getFilename(); - $hash = $this->getHash(); - $existingVariant = $this->getVariant(); - if ($existingVariant) { - $variant = $existingVariant . '_' . $variant; - } - - // Skip empty files (e.g. Folder does not have a hash) - if (empty($filename) || empty($hash)) { - return null; - } - - // Create this asset in the store if it doesn't already exist, - // otherwise use the existing variant - $store = Injector::inst()->get('AssetStore'); - $result = null; - if (!$store->exists($filename, $hash, $variant)) { - $result = call_user_func($callback, $store, $filename, $hash, $variant); - } else { - $result = array( - 'Filename' => $filename, - 'Hash' => $hash, - 'Variant' => $variant - ); - } - - // Callback may fail to perform this manipulation (e.g. resize on text file) - if (!$result) { - return null; - } - - // Store result in new DBFile instance - /** @var DBFile $file */ - $file = DBField::create_field('DBFile', $result); - return $file->setOriginal($this); - } - - /** - * Name a variant based on a format with arbitrary parameters - * - * @param string $format The format name. - * @param mixed $arg,... Additional arguments - * @return string - * @throws InvalidArgumentException - */ - public function variantName($format, $arg = null) - { - $args = func_get_args(); - array_shift($args); - return $format . Convert::base64url_encode($args); - } -} diff --git a/src/Assets/Image_Backend.php b/src/Assets/Image_Backend.php deleted file mode 100644 index a73af65e8..000000000 --- a/src/Assets/Image_Backend.php +++ /dev/null @@ -1,151 +0,0 @@ -loadFromContainer($assetContainer); - } - } - - public function loadFromContainer(AssetContainer $assetContainer) - { - $stream = $assetContainer->getStream(); - $this->readImageFile($stream); - fclose($stream); - $this->setDefaultQuality(); - } - - public function loadFrom($path) - { - $this->readImage($path); - $this->setDefaultQuality(); - } - - protected function setDefaultQuality() - { - $this->setQuality(Config::inst()->get(__CLASS__, 'default_quality')); - } - - public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $variant = null, $config = array()) - { - // Write to temporary file, taking care to maintain the extension - $path = tempnam(sys_get_temp_dir(), 'imagemagick'); - if ($extension = pathinfo($filename, PATHINFO_EXTENSION)) { - $path .= "." . $extension; - } - $this->writeImage($path); - $result = $assetStore->setFromLocalFile($path, $filename, $hash, $variant, $config); - unlink($path); - return $result; - } - - public function writeTo($path) - { - Filesystem::makeFolder(dirname($path)); - if (is_dir(dirname($path))) { - $this->writeImage($path); - } - } - - public function setQuality($quality) - { - $this->setImageCompressionQuality($quality); - } - - public function resize($width, $height) - { - if (!$this->valid()) { - return null; - } - - if ($width < 0 || $height < 0) { - throw new InvalidArgumentException("Image resizing dimensions cannot be negative"); - } - if (!$width && !$height) { - throw new InvalidArgumentException("No dimensions given when resizing image"); - } - if (!$width) { - throw new InvalidArgumentException("Width not given when resizing image"); - } - if (!$height) { - throw new InvalidArgumentException("Height not given when resizing image"); - } - - //use whole numbers, ensuring that size is at least 1x1 - $width = max(1, round($width)); - $height = max(1, round($height)); - - $geometry = $this->getImageGeometry(); - - // Check that a resize is actually necessary. - if ($width === $geometry["width"] && $height === $geometry["height"]) { - return $this; - } - - $new = clone $this; - $new->resizeImage($width, $height, self::FILTER_LANCZOS, 1); - - return $new; - } - - public function resizeRatio($maxWidth, $maxHeight, $useAsMinimum = false) - { - if (!$this->valid()) { - return null; - } - - $geometry = $this->getImageGeometry(); - - $widthRatio = $maxWidth / $geometry["width"]; - $heightRatio = $maxHeight / $geometry["height"]; - - if ($widthRatio < $heightRatio) { - return $useAsMinimum - ? $this->resizeByHeight($maxHeight) - : $this->resizeByWidth($maxWidth); - } else { - return $useAsMinimum - ? $this->resizeByWidth($maxWidth) - : $this->resizeByHeight($maxHeight); - } - } - - public function resizeByWidth($width) - { - if (!$this->valid()) { - return null; - } - - $geometry = $this->getImageGeometry(); - - $heightScale = $width / $geometry["width"]; - return $this->resize($width, $heightScale * $geometry["height"]); - } - - public function resizeByHeight($height) - { - if (!$this->valid()) { - return null; - } - - $geometry = $this->getImageGeometry(); - - $scale = $height / $geometry["height"]; - return $this->resize($scale * $geometry["width"], $height); - } - - /** - * paddedResize - * - * @param int $width - * @param int $height - * @param string $backgroundColor - * @param int $transparencyPercent - * @return Image_Backend - */ - public function paddedResize($width, $height, $backgroundColor = "FFFFFF", $transparencyPercent = 0) - { - if (!$this->valid()) { - return null; - } - - //keep the % within bounds of 0-100 - $transparencyPercent = min(100, max(0, $transparencyPercent)); - - $new = $this->resizeRatio($width, $height); - if ($transparencyPercent) { - $alphaHex = $this->calculateAlphaHex($transparencyPercent); - $new->setImageBackgroundColor("#{$backgroundColor}{$alphaHex}"); - } else { - $new->setImageBackgroundColor("#{$backgroundColor}"); - } - $w = $new->getImageWidth(); - $h = $new->getImageHeight(); - $new->extentImage($width, $height, ($w-$width)/2, ($h-$height)/2); - return $new; - } - - /** - * Convert a percentage (or 'true') to a two char hex code to signifiy the level of an alpha channel - * - * @param $percent - * @return string - */ - public function calculateAlphaHex($percent) - { - if ($percent > 100) { - $percent = 100; - } - // unlike GD, this uses 255 instead of 127, and is reversed. Lower = more transparent - $alphaHex = dechex(255 - floor(255 * bcdiv($percent, 100, 2))); - if (strlen($alphaHex) == 1) { - $alphaHex = '0' .$alphaHex; - } - return $alphaHex; - } - - - /** - * croppedResize - * - * @param int $width - * @param int $height - * @return Image_Backend - */ - public function croppedResize($width, $height) - { - if (!$this->valid()) { - return null; - } - - $width = round($width); - $height = round($height); - $geo = $this->getImageGeometry(); - - // Check that a resize is actually necessary. - if ($width == $geo["width"] && $height == $geo["height"]) { - return $this; - } - - $new = clone $this; - $new->setBackgroundColor(new ImagickPixel('transparent')); - - if (($geo['width']/$width) < ($geo['height']/$height)) { - $new->cropImage( - $geo['width'], - floor($height*$geo['width']/$width), - 0, - ($geo['height'] - ($height*$geo['width']/$width))/2 - ); - } else { - $new->cropImage( - ceil($width*$geo['height']/$height), - $geo['height'], - ($geo['width'] - ($width*$geo['height']/$height))/2, - 0 - ); - } - $new->thumbnailImage($width, $height, true); - return $new; - } - - /** - * Crop's part of image. - * @param int $top y position of left upper corner of crop rectangle - * @param int $left x position of left upper corner of crop rectangle - * @param int $width rectangle width - * @param int $height rectangle height - * @return Image_Backend - */ - public function crop($top, $left, $width, $height) - { - $new = clone $this; - $new->cropImage($width, $height, $left, $top); - - return $new; - } -} diff --git a/src/Assets/Storage/AssetContainer.php b/src/Assets/Storage/AssetContainer.php deleted file mode 100644 index 8a4a2ec77..000000000 --- a/src/Assets/Storage/AssetContainer.php +++ /dev/null @@ -1,187 +0,0 @@ -setAllowedCategories($allowed); - } - - /** - * Determine if a valid non-empty image exists behind this asset, which is a format - * compatible with image manipulations - * - * @return boolean - */ - public function getIsImage() - { - // Check file type - $mime = $this->getMimeType(); - return $mime && in_array($mime, $this->config()->supported_images); - } - - /** - * @return AssetStore - */ - protected function getStore() - { - return Injector::inst()->get('AssetStore'); - } - - private static $composite_db = array( - "Hash" => "Varchar(255)", // SHA of the base content - "Filename" => "Varchar(255)", // Path identifier of the base content - "Variant" => "Varchar(255)", // Identifier of the variant to the base, if given - ); - - private static $casting = array( - 'URL' => 'Varchar', - 'AbsoluteURL' => 'Varchar', - 'Basename' => 'Varchar', - 'Title' => 'Varchar', - 'MimeType' => 'Varchar', - 'String' => 'Text', - 'Tag' => 'HTMLFragment', - 'Size' => 'Varchar' - ); - - public function scaffoldFormField($title = null, $params = null) - { - return null; - } - - /** - * Return a html5 tag of the appropriate for this file (normally img or a) - * - * @return string - */ - public function XML() - { - return $this->getTag() ?: ''; - } - - /** - * Return a html5 tag of the appropriate for this file (normally img or a) - * - * @return string - */ - public function getTag() - { - $template = $this->getFrontendTemplate(); - if (empty($template)) { - return ''; - } - return (string)$this->renderWith($template); - } - - /** - * Determine the template to render as on the frontend - * - * @return string Name of template - */ - public function getFrontendTemplate() - { - // Check that path is available - $url = $this->getURL(); - if (empty($url)) { - return null; - } - - // Image template for supported images - if ($this->getIsImage()) { - return 'DBFile_image'; - } - - // Default download - return 'DBFile_download'; - } - - /** - * Get trailing part of filename - * - * @return string - */ - public function getBasename() - { - if (!$this->exists()) { - return null; - } - return basename($this->getSourceURL()); - } - - /** - * Get file extension - * - * @return string - */ - public function getExtension() - { - if (!$this->exists()) { - return null; - } - return pathinfo($this->Filename, PATHINFO_EXTENSION); - } - - /** - * Alt title for this - * - * @return string - */ - public function getTitle() - { - // If customised, use the customised title - if ($this->failover && ($title = $this->failover->Title)) { - return $title; - } - // fallback to using base name - return $this->getBasename(); - } - - public function setFromLocalFile($path, $filename = null, $hash = null, $variant = null, $config = array()) - { - $this->assertFilenameValid($filename ?: $path); - $result = $this - ->getStore() - ->setFromLocalFile($path, $filename, $hash, $variant, $config); - // Update from result - if ($result) { - $this->setValue($result); - } - return $result; - } - - public function setFromStream($stream, $filename, $hash = null, $variant = null, $config = array()) - { - $this->assertFilenameValid($filename); - $result = $this - ->getStore() - ->setFromStream($stream, $filename, $hash, $variant, $config); - // Update from result - if ($result) { - $this->setValue($result); - } - return $result; - } - - public function setFromString($data, $filename, $hash = null, $variant = null, $config = array()) - { - $this->assertFilenameValid($filename); - $result = $this - ->getStore() - ->setFromString($data, $filename, $hash, $variant, $config); - // Update from result - if ($result) { - $this->setValue($result); - } - return $result; - } - - public function getStream() - { - if (!$this->exists()) { - return null; - } - return $this - ->getStore() - ->getAsStream($this->Filename, $this->Hash, $this->Variant); - } - - public function getString() - { - if (!$this->exists()) { - return null; - } - return $this - ->getStore() - ->getAsString($this->Filename, $this->Hash, $this->Variant); - } - - public function getURL($grant = true) - { - if (!$this->exists()) { - return null; - } - $url = $this->getSourceURL($grant); - $this->updateURL($url); - $this->extend('updateURL', $url); - return $url; - } - - /** - * Get URL, but without resampling. - * Note that this will return the url even if the file does not exist. - * - * @param bool $grant Ensures that the url for any protected assets is granted for the current user. - * @return string - */ - public function getSourceURL($grant = true) - { - return $this - ->getStore() - ->getAsURL($this->Filename, $this->Hash, $this->Variant, $grant); - } - - /** - * Get the absolute URL to this resource - * - * @return string - */ - public function getAbsoluteURL() - { - if (!$this->exists()) { - return null; - } - return Director::absoluteURL($this->getURL()); - } - - public function getMetaData() - { - if (!$this->exists()) { - return null; - } - return $this - ->getStore() - ->getMetadata($this->Filename, $this->Hash, $this->Variant); - } - - public function getMimeType() - { - if (!$this->exists()) { - return null; - } - return $this - ->getStore() - ->getMimeType($this->Filename, $this->Hash, $this->Variant); - } - - public function getValue() - { - if (!$this->exists()) { - return null; - } - return array( - 'Filename' => $this->Filename, - 'Hash' => $this->Hash, - 'Variant' => $this->Variant - ); - } - - public function getVisibility() - { - if (empty($this->Filename)) { - return null; - } - return $this - ->getStore() - ->getVisibility($this->Filename, $this->Hash); - } - - public function exists() - { - if (empty($this->Filename)) { - return false; - } - return $this - ->getStore() - ->exists($this->Filename, $this->Hash, $this->Variant); - } - - public function getFilename() - { - return $this->getField('Filename'); - } - - public function getHash() - { - return $this->getField('Hash'); - } - - public function getVariant() - { - return $this->getField('Variant'); - } - - /** - * Return file size in bytes. - * - * @return int - */ - public function getAbsoluteSize() - { - $metadata = $this->getMetaData(); - if (isset($metadata['size'])) { - return $metadata['size']; - } - return 0; - } - - /** - * Customise this object with an "original" record for getting other customised fields - * - * @param AssetContainer $original - * @return $this - */ - public function setOriginal($original) - { - $this->failover = $original; - return $this; - } - - /** - * Get list of allowed file categories - * - * @return array - */ - public function getAllowedCategories() - { - return $this->allowedCategories; - } - - /** - * Assign allowed categories - * - * @param array|string $categories - * @return $this - */ - public function setAllowedCategories($categories) - { - if (is_string($categories)) { - $categories = preg_split('/\s*,\s*/', $categories); - } - $this->allowedCategories = (array)$categories; - return $this; - } - - /** - * Gets the list of extensions (if limited) for this field. Empty list - * means there is no restriction on allowed types. - * - * @return array - */ - protected function getAllowedExtensions() - { - $categories = $this->getAllowedCategories(); - return File::get_category_extensions($categories); - } - - /** - * Validate that this DBFile accepts this filename as valid - * - * @param string $filename - * @throws ValidationException - * @return bool - */ - protected function isValidFilename($filename) - { - $extension = strtolower(File::get_file_extension($filename)); - - // Validate true if within the list of allowed extensions - $allowed = $this->getAllowedExtensions(); - if ($allowed) { - return in_array($extension, $allowed); - } - - // If no extensions are configured, fallback to global list - $globalList = File::config()->allowed_extensions; - if (in_array($extension, $globalList)) { - return true; - } - - // Only admins can bypass global rules - return !File::config()->apply_restrictions_to_admin && Permission::check('ADMIN'); - } - - /** - * Check filename, and raise a ValidationException if invalid - * - * @param string $filename - * @throws ValidationException - */ - protected function assertFilenameValid($filename) - { - $result = new ValidationResult(); - $this->validate($result, $filename); - if (!$result->isValid()) { - throw new ValidationException($result); - } - } - - - /** - * Hook to validate this record against a validation result - * - * @param ValidationResult $result - * @param string $filename Optional filename to validate. If omitted, the current value is validated. - * @return bool Valid flag - */ - public function validate(ValidationResult $result, $filename = null) - { - if (empty($filename)) { - $filename = $this->getFilename(); - } - if (empty($filename) || $this->isValidFilename($filename)) { - return true; - } - - $message = _t('File.INVALIDEXTENSIONSHORT', 'Extension is not allowed'); - $result->addError($message); - return false; - } - - public function setField($field, $value, $markChanged = true) - { - // Catch filename validation on direct assignment - if ($field === 'Filename' && $value) { - $this->assertFilenameValid($value); - } - - return parent::setField($field, $value, $markChanged); - } - - - /** - * Returns the size of the file type in an appropriate format. - * - * @return string|false String value, or false if doesn't exist - */ - public function getSize() - { - $size = $this->getAbsoluteSize(); - if ($size) { - return File::format_size($size); - } - return false; - } - - public function deleteFile() - { - if (!$this->Filename) { - return false; - } - - return $this - ->getStore() - ->delete($this->Filename, $this->Hash); - } - - public function publishFile() - { - if ($this->Filename) { - $this - ->getStore() - ->publish($this->Filename, $this->Hash); - } - } - - public function protectFile() - { - if ($this->Filename) { - $this - ->getStore() - ->protect($this->Filename, $this->Hash); - } - } - - public function grantFile() - { - if ($this->Filename) { - $this - ->getStore() - ->grant($this->Filename, $this->Hash); - } - } - - public function revokeFile() - { - if ($this->Filename) { - $this - ->getStore() - ->revoke($this->Filename, $this->Hash); - } - } - - public function canViewFile() - { - return $this->Filename - && $this - ->getStore() - ->canView($this->Filename, $this->Hash); - } -} diff --git a/src/Assets/Storage/DefaultAssetNameGenerator.php b/src/Assets/Storage/DefaultAssetNameGenerator.php deleted file mode 100644 index c445b55d6..000000000 --- a/src/Assets/Storage/DefaultAssetNameGenerator.php +++ /dev/null @@ -1,170 +0,0 @@ -filename = $filename; - $this->directory = ltrim(dirname($filename), '.'); - $name = basename($this->filename); - // Note: Unlike normal extensions, we want to split at the first period, not the last. - if (($pos = strpos($name, '.')) !== false) { - $this->extension = substr($name, $pos); - $name = substr($name, 0, $pos); - } else { - $this->extension = null; - } - - // Extract version prefix if already applied to this file - $this->padding = 0; - $pattern = '/^(?[^\/]+?)' . preg_quote($this->getPrefix()) . '(?[0-9]+)$/'; - if (preg_match($pattern, $name, $matches)) { - $this->first = (int)$matches['version']; - $this->name = $matches['name']; - // Check if number is padded - if (strpos($matches['version'], '0') === 0) { - $this->padding = strlen($matches['version']); - } - } else { - $this->first = 1; - $this->name = $name; - } - - $this->rewind(); - } - - /** - * Get numeric prefix - * - * @return string - */ - protected function getPrefix() - { - return Config::inst()->get(__CLASS__, 'version_prefix'); - } - - public function current() - { - $version = $this->version; - - // Initially suggest original name - if ($version === $this->first) { - return $this->filename; - } - - // If there are more than $this->max files we need a new scheme - if ($version >= $this->max + $this->first - 1) { - $version = substr(md5(time()), 0, 10); - } elseif ($this->padding) { - // Else, pad - $version = str_pad($version, $this->padding, '0', STR_PAD_LEFT); - } - - // Build next name - $filename = $this->name . $this->getPrefix() . $version . $this->extension; - if ($this->directory) { - $filename = $this->directory . DIRECTORY_SEPARATOR . $filename; - } - return $filename; - } - - public function key() - { - return $this->version - $this->first; - } - - public function next() - { - $this->version++; - } - - public function rewind() - { - $this->version = $this->first; - } - - public function valid() - { - return $this->version < $this->max + $this->first; - } - - public function getMaxTries() - { - return $this->max; - } -} diff --git a/src/Assets/Storage/GeneratedAssetHandler.php b/src/Assets/Storage/GeneratedAssetHandler.php deleted file mode 100644 index 97ed4f68c..000000000 --- a/src/Assets/Storage/GeneratedAssetHandler.php +++ /dev/null @@ -1,53 +0,0 @@ -handler; - } - - /** - * @param AssetStoreRouter $handler - * @return $this - */ - public function setRouteHandler(AssetStoreRouter $handler) - { - $this->handler = $handler; - return $this; - } - - private static $url_handlers = array( - '$Filename' => "handleFile" - ); - - private static $allowed_actions = array( - 'handleFile' - ); - - /** - * Provide a response for the given file request - * - * @param HTTPRequest $request - * @return HTTPResponse - */ - public function handleFile(HTTPRequest $request) - { - $filename = $this->parseFilename($request); - - // Deny requests to private file - if (!$this->isValidFilename($filename)) { - return $this->httpError(400, "Invalid request"); - } - - // Pass through to backend - return $this->getRouteHandler()->getResponseFor($filename); - } - - /** - * Check if the given filename is safe to pass to the route handler. - * This should block direct requests to assets/.protected/ paths - * - * @param $filename - * @return bool True if the filename is allowed - */ - public function isValidFilename($filename) - { - // Block hidden files - return !preg_match('#(^|[\\\\/])\\..*#', $filename); - } - - /** - * Get the file component from the request - * - * @param HTTPRequest $request - * @return string - */ - protected function parseFilename(HTTPRequest $request) - { - $filename = ''; - $next = $request->param('Filename'); - while ($next) { - $filename = $filename ? File::join_paths($filename, $next) : $next; - $next = $request->shift(); - } - if ($extension = $request->getExtension()) { - $filename = $filename . "." . $extension; - } - return $filename; - } -} diff --git a/src/Assets/Thumbnail.php b/src/Assets/Thumbnail.php deleted file mode 100644 index b42891e7a..000000000 --- a/src/Assets/Thumbnail.php +++ /dev/null @@ -1,19 +0,0 @@ -Validation - * - * By default, a user can upload files without extension limitations, - * which can be a security risk if the webserver is not properly secured. - * Use {@link setAllowedExtensions()} to limit this list, - * and ensure the "assets/" directory does not execute scripts - * (see http://doc.silverstripe.org/secure-development#filesystem). - * {@link File::$allowed_extensions} provides a good start for a list of "safe" extensions. - * - * @todo Allow for non-database uploads - */ -class Upload extends Controller -{ - - private static $allowed_actions = array( - 'index', - 'load' - ); - - /** - * A dataobject (typically {@see File}) which implements {@see AssetContainer} - * - * @var AssetContainer - */ - protected $file; - - /** - * Validator for this upload field - * - * @var Upload_Validator - */ - protected $validator; - - /** - * Information about the temporary file produced - * by the PHP-runtime. - * - * @var array - */ - protected $tmpFile; - - /** - * Replace an existing file rather than renaming the new one. - * - * @var boolean - */ - protected $replaceFile = false; - - /** - * Processing errors that can be evaluated, - * e.g. by Form-validation. - * - * @var array - */ - protected $errors = array(); - - /** - * Default visibility to assign uploaded files - * - * @var string - */ - protected $defaultVisibility = AssetStore::VISIBILITY_PROTECTED; - - /** - * A foldername relative to /assets, - * where all uploaded files are stored by default. - * - * @config - * @var string - */ - private static $uploads_folder = "Uploads"; - - /** - * A prefix for the version number added to an uploaded file - * when a file with the same name already exists. - * Example using no prefix: IMG001.jpg becomes IMG2.jpg - * Example using '-v' prefix: IMG001.jpg becomes IMG001-v2.jpg - * - * @config - * @var string - */ - private static $version_prefix = '-v'; - - public function __construct() - { - parent::__construct(); - $this->validator = Upload_Validator::create(); - $this->replaceFile = self::config()->replaceFile; - } - - public function index() - { - return $this->httpError(404); // no-op - } - - /** - * Get current validator - * - * @return Upload_Validator $validator - */ - public function getValidator() - { - return $this->validator; - } - - /** - * Set a different instance than {@link Upload_Validator} - * for this upload session. - * - * @param object $validator - */ - public function setValidator($validator) - { - $this->validator = $validator; - } - - - /** - * Get an asset renamer for the given filename. - * - * @param string $filename Path name - * @return AssetNameGenerator - */ - protected function getNameGenerator($filename) - { - return Injector::inst()->createWithArgs('AssetNameGenerator', array($filename)); - } - - /** - * - * @return AssetStore - */ - protected function getAssetStore() - { - return Injector::inst()->get('AssetStore'); - } - - /** - * Save an file passed from a form post into the AssetStore directly - * - * @param array $tmpFile Indexed array that PHP generated for every file it uploads. - * @param string|bool $folderPath Folder path relative to /assets - * @return array|false Either the tuple array, or false if the file could not be saved - */ - public function load($tmpFile, $folderPath = false) - { - // Validate filename - $filename = $this->getValidFilename($tmpFile, $folderPath); - if (!$filename) { - return false; - } - - // Save file into backend - $result = $this->storeTempFile($tmpFile, $filename, $this->getAssetStore()); - - //to allow extensions to e.g. create a version after an upload - $this->extend('onAfterLoad', $result, $tmpFile); - return $result; - } - - /** - * Save an file passed from a form post into this object. - * File names are filtered through {@link FileNameFilter}, see class documentation - * on how to influence this behaviour. - * - * @param array $tmpFile - * @param AssetContainer $file - * @param string|bool $folderPath - * @return bool True if the file was successfully saved into this record - * @throws Exception - */ - public function loadIntoFile($tmpFile, $file = null, $folderPath = false) - { - $this->file = $file; - - // Validate filename - $filename = $this->getValidFilename($tmpFile, $folderPath); - if (!$filename) { - return false; - } - $filename = $this->resolveExistingFile($filename); - - // Save changes to underlying record (if it's a DataObject) - $this->storeTempFile($tmpFile, $filename, $this->file); - if ($this->file instanceof DataObject) { - $this->file->write(); - } - - //to allow extensions to e.g. create a version after an upload - $this->file->extend('onAfterUpload'); - $this->extend('onAfterLoadIntoFile', $this->file); - return true; - } - - /** - * Assign this temporary file into the given destination - * - * @param array $tmpFile - * @param string $filename - * @param AssetContainer|AssetStore $container - * @return array - */ - protected function storeTempFile($tmpFile, $filename, $container) - { - // Save file into backend - $conflictResolution = $this->replaceFile - ? AssetStore::CONFLICT_OVERWRITE - : AssetStore::CONFLICT_RENAME; - $config = array( - 'conflict' => $conflictResolution, - 'visibility' => $this->getDefaultVisibility() - ); - return $container->setFromLocalFile($tmpFile['tmp_name'], $filename, null, null, $config); - } - - /** - * Given a temporary file and upload path, validate the file and determine the - * value of the 'Filename' tuple that should be used to store this asset. - * - * @param array $tmpFile - * @param string $folderPath - * @return string|false Value of filename tuple, or false if invalid - */ - protected function getValidFilename($tmpFile, $folderPath = null) - { - if (!is_array($tmpFile)) { - throw new InvalidArgumentException( - "Upload::load() Not passed an array. Most likely, the form hasn't got the right enctype" - ); - } - - // Validate - $this->clearErrors(); - $valid = $this->validate($tmpFile); - if (!$valid) { - return false; - } - - // Clean filename - if (!$folderPath) { - $folderPath = $this->config()->uploads_folder; - } - $nameFilter = FileNameFilter::create(); - $file = $nameFilter->filter($tmpFile['name']); - $filename = basename($file); - if ($folderPath) { - $filename = File::join_paths($folderPath, $filename); - } - return $filename; - } - - /** - * Given a file and filename, ensure that file renaming / replacing rules are satisfied - * - * If replacing, this method may replace $this->file with an existing record to overwrite. - * If renaming, a new value for $filename may be returned - * - * @param string $filename - * @return string $filename A filename safe to write to - * @throws Exception - */ - protected function resolveExistingFile($filename) - { - // Create a new file record (or try to retrieve an existing one) - if (!$this->file) { - $fileClass = File::get_class_for_file_extension( - File::get_file_extension($filename) - ); - $this->file = Object::create($fileClass); - } - - // Skip this step if not writing File dataobjects - if (! ($this->file instanceof File)) { - return $filename; - } - - // Check there is if existing file - $existing = File::find($filename); - - // If replacing (or no file exists) confirm this filename is safe - if ($this->replaceFile || !$existing) { - // If replacing files, make sure to update the OwnerID - if (!$this->file->ID && $this->replaceFile && $existing) { - $this->file = $existing; - $this->file->OwnerID = Member::currentUserID(); - } - // Filename won't change if replacing - return $filename; - } - - // if filename already exists, version the filename (e.g. test.gif to test-v2.gif, test-v2.gif to test-v3.gif) - $renamer = $this->getNameGenerator($filename); - foreach ($renamer as $newName) { - if (!File::find($newName)) { - return $newName; - } - } - - // Fail - $tries = $renamer->getMaxTries(); - throw new Exception("Could not rename {$filename} with {$tries} tries"); - } - - /** - * @param bool $replace - */ - public function setReplaceFile($replace) - { - $this->replaceFile = $replace; - } - - /** - * @return bool - */ - public function getReplaceFile() - { - return $this->replaceFile; - } - - /** - * Container for all validation on the file - * (e.g. size and extension restrictions). - * Is NOT connected to the {Validator} classes, - * please have a look at {FileField->validate()} - * for an example implementation of external validation. - * - * @param array $tmpFile - * @return boolean - */ - public function validate($tmpFile) - { - $validator = $this->validator; - $validator->setTmpFile($tmpFile); - $isValid = $validator->validate(); - if ($validator->getErrors()) { - $this->errors = array_merge($this->errors, $validator->getErrors()); - } - return $isValid; - } - - /** - * Get file-object, either generated from {load()}, - * or manually set. - * - * @return AssetContainer - */ - public function getFile() - { - return $this->file; - } - - /** - * Set a file-object (similiar to {loadIntoFile()}) - * - * @param AssetContainer $file - */ - public function setFile(AssetContainer $file) - { - $this->file = $file; - } - - /** - * Clear out all errors (mostly set by {loadUploaded()}) - * including the validator's errors - */ - public function clearErrors() - { - $this->errors = array(); - $this->validator->clearErrors(); - } - - /** - * Determines wether previous operations caused an error. - * - * @return boolean - */ - public function isError() - { - return (count($this->errors)); - } - - /** - * Return all errors that occurred while processing so far - * (mostly set by {loadUploaded()}) - * - * @return array - */ - public function getErrors() - { - return $this->errors; - } - - /** - * Get default visibility for uploaded files. {@see AssetStore} - * One of the values of AssetStore::VISIBILITY_* constants - * - * @return string - */ - public function getDefaultVisibility() - { - return $this->defaultVisibility; - } - - /** - * Assign default visibility for uploaded files. {@see AssetStore} - * One of the values of AssetStore::VISIBILITY_* constants - * - * @param string $visibility - * @return $this - */ - public function setDefaultVisibility($visibility) - { - $this->defaultVisibility = $visibility; - return $this; - } -} diff --git a/src/Assets/Upload_Validator.php b/src/Assets/Upload_Validator.php deleted file mode 100644 index 11fd4c3c2..000000000 --- a/src/Assets/Upload_Validator.php +++ /dev/null @@ -1,313 +0,0 @@ - - * array("jpg","GIF") - * - */ - public $allowedExtensions = array(); - - /** - * Return all errors that occurred while validating - * the temporary file. - * - * @return array - */ - public function getErrors() - { - return $this->errors; - } - - /** - * Clear out all errors - */ - public function clearErrors() - { - $this->errors = array(); - } - - /** - * Set information about temporary file produced by PHP. - * @param array $tmpFile - */ - public function setTmpFile($tmpFile) - { - $this->tmpFile = $tmpFile; - } - - /** - * Get maximum file size for all or specified file extension. - * - * @param string $ext - * @return int Filesize in bytes - */ - public function getAllowedMaxFileSize($ext = null) - { - - // Check if there is any defined instance max file sizes - if (empty($this->allowedMaxFileSize)) { - // Set default max file sizes if there isn't - $fileSize = Config::inst()->get(__CLASS__, 'default_max_file_size'); - if ($fileSize) { - $this->setAllowedMaxFileSize($fileSize); - } else { - // When no default is present, use maximum set by PHP - $maxUpload = File::ini2bytes(ini_get('upload_max_filesize')); - $maxPost = File::ini2bytes(ini_get('post_max_size')); - $this->setAllowedMaxFileSize(min($maxUpload, $maxPost)); - } - } - - $ext = strtolower($ext); - if ($ext) { - if (isset($this->allowedMaxFileSize[$ext])) { - return $this->allowedMaxFileSize[$ext]; - } - - $category = File::get_app_category($ext); - if ($category && isset($this->allowedMaxFileSize['[' . $category . ']'])) { - return $this->allowedMaxFileSize['[' . $category . ']']; - } - } - - return (isset($this->allowedMaxFileSize['*'])) ? $this->allowedMaxFileSize['*'] : false; - } - - /** - * Set filesize maximums (in bytes or INI format). - * Automatically converts extensions to lowercase - * for easier matching. - * - * Example: - * - * array('*' => 200, 'jpg' => 1000, '[doc]' => '5m') - * - * - * @param array|int $rules - */ - public function setAllowedMaxFileSize($rules) - { - if (is_array($rules) && count($rules)) { - // make sure all extensions are lowercase - $rules = array_change_key_case($rules, CASE_LOWER); - $finalRules = array(); - - foreach ($rules as $rule => $value) { - if (is_numeric($value)) { - $tmpSize = $value; - } else { - $tmpSize = File::ini2bytes($value); - } - - $finalRules[$rule] = (int)$tmpSize; - } - - $this->allowedMaxFileSize = $finalRules; - } elseif (is_string($rules)) { - $this->allowedMaxFileSize['*'] = File::ini2bytes($rules); - } elseif ((int)$rules > 0) { - $this->allowedMaxFileSize['*'] = (int)$rules; - } - } - - /** - * @return array - */ - public function getAllowedExtensions() - { - return $this->allowedExtensions; - } - - /** - * Limit allowed file extensions. Empty by default, allowing all extensions. - * To allow files without an extension, use an empty string. - * See {@link File::$allowed_extensions} to get a good standard set of - * extensions that are typically not harmful in a webserver context. - * See {@link setAllowedMaxFileSize()} to limit file size by extension. - * - * @param array $rules List of extensions - */ - public function setAllowedExtensions($rules) - { - if (!is_array($rules)) { - return; - } - - // make sure all rules are lowercase - foreach ($rules as &$rule) { - $rule = strtolower($rule); - } - - $this->allowedExtensions = $rules; - } - - /** - * Determines if the bytesize of an uploaded - * file is valid - can be defined on an - * extension-by-extension basis in {@link $allowedMaxFileSize} - * - * @return boolean - */ - public function isValidSize() - { - // If file was blocked via PHP for being excessive size, shortcut here - switch ($this->tmpFile['error']) { - case UPLOAD_ERR_INI_SIZE: - case UPLOAD_ERR_FORM_SIZE: - return false; - } - $pathInfo = pathinfo($this->tmpFile['name']); - $extension = isset($pathInfo['extension']) ? strtolower($pathInfo['extension']) : null; - $maxSize = $this->getAllowedMaxFileSize($extension); - return (!$this->tmpFile['size'] || !$maxSize || (int)$this->tmpFile['size'] < $maxSize); - } - - /** - * Determine if this file is valid but empty - * - * @return bool - */ - public function isFileEmpty() - { - // Don't check file size for errors - if ($this->tmpFile['error'] !== UPLOAD_ERR_OK) { - return false; - } - return empty($this->tmpFile['size']); - } - - /** - * Determines if the temporary file has a valid extension - * An empty string in the validation map indicates files without an extension. - * @return boolean - */ - public function isValidExtension() - { - $pathInfo = pathinfo($this->tmpFile['name']); - - // Special case for filenames without an extension - if (!isset($pathInfo['extension'])) { - return in_array('', $this->allowedExtensions, true); - } else { - return (!count($this->allowedExtensions) - || in_array(strtolower($pathInfo['extension']), $this->allowedExtensions)); - } - } - - /** - * Run through the rules for this validator checking against - * the temporary file set by {@link setTmpFile()} to see if - * the file is deemed valid or not. - * - * @return boolean - */ - public function validate() - { - // we don't validate for empty upload fields yet - if (empty($this->tmpFile['name'])) { - return true; - } - - // Check file upload - if (!$this->isValidUpload()) { - $this->errors[] = _t('File.NOVALIDUPLOAD', 'File is not a valid upload'); - return false; - } - - // Check file isn't empty - if ($this->isFileEmpty()) { - $this->errors[] = _t('File.NOFILESIZE', 'Filesize is zero bytes.'); - return false; - } - - // filesize validation - if (!$this->isValidSize()) { - $pathInfo = pathinfo($this->tmpFile['name']); - $ext = (isset($pathInfo['extension'])) ? $pathInfo['extension'] : ''; - $arg = File::format_size($this->getAllowedMaxFileSize($ext)); - $this->errors[] = _t( - 'File.TOOLARGE', - 'Filesize is too large, maximum {size} allowed', - 'Argument 1: Filesize (e.g. 1MB)', - array('size' => $arg) - ); - return false; - } - - // extension validation - if (!$this->isValidExtension()) { - $this->errors[] = _t( - 'File.INVALIDEXTENSION_SHORT', - 'Extension is not allowed' - ); - return false; - } - - return true; - } - - /** - * Check that a valid file was given for upload (ignores file size) - * - * @return bool - */ - public function isValidUpload() - { - // Check file upload - if ($this->tmpFile['error'] === UPLOAD_ERR_NO_FILE) { - return false; - } - - // Check if file is valid uploaded (with exception for unit testing) - // Note that some "max file size" errors leave "temp_name" empty, so don't fail on this. - $isRunningTests = (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test()); - if (!empty($this->tmpFile['tmp_name']) && !is_uploaded_file($this->tmpFile['tmp_name']) && !$isRunningTests) { - return false; - } - - return true; - } -} diff --git a/src/Core/Extensible.php b/src/Core/Extensible.php index 6b4953d90..7b4e4d893 100644 --- a/src/Core/Extensible.php +++ b/src/Core/Extensible.php @@ -36,7 +36,7 @@ trait Extensible * @var array $extensions * @config */ - private static $extensions = null; + private static $extensions = []; private static $classes_constructed = array(); diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index 2bd3e8377..73fc68c32 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -2,7 +2,6 @@ namespace SilverStripe\ORM; -use SilverStripe\Assets\AssetControlExtension; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Object; @@ -20,7 +19,6 @@ use SilverStripe\ORM\Filters\SearchFilter; use SilverStripe\ORM\Search\SearchContext; use SilverStripe\ORM\Queries\SQLInsert; use SilverStripe\ORM\Queries\SQLDelete; -use SilverStripe\ORM\Queries\SQLSelect; use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBComposite; @@ -247,16 +245,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity 'Created' => 'DBDatetime', ); - /** - * Core dataobject extensions - * - * @config - * @var array - */ - private static $extensions = [ - 'AssetControl' => AssetControlExtension::class, - ]; - /** * Override table name for this class. If ignored will default to FQN of class. * This option is not inheritable, and must be set on each class. diff --git a/templates/DBFile_download.ss b/templates/DBFile_download.ss deleted file mode 100644 index fc9c910db..000000000 --- a/templates/DBFile_download.ss +++ /dev/null @@ -1 +0,0 @@ -download="$Basename.ATT"<% else %>download<% end_if %>/> diff --git a/templates/DBFile_image.ss b/templates/DBFile_image.ss deleted file mode 100644 index 07ccde96e..000000000 --- a/templates/DBFile_image.ss +++ /dev/null @@ -1 +0,0 @@ -$Title.ATT diff --git a/templates/Image_iframe.ss b/templates/Image_iframe.ss deleted file mode 100644 index 0f1139fbe..000000000 --- a/templates/Image_iframe.ss +++ /dev/null @@ -1,28 +0,0 @@ - - - - <% base_tag %> - <%t Image_iframe_ss.TITLE 'Image Uploading Iframe' %> - - - -
    - <% if $UseSimpleForm %> - $EditImageSimpleForm - <% else %> - $EditImageForm - <% end_if %> -
    - - <% if $Image.ID %> -
    - $Image.CMSThumbnail - <% if $DeleteImageForm %> - $DeleteImageForm - <% end_if %> -
    - <% end_if %> - - - - diff --git a/templates/SilverStripe/Assets/Flysystem/ProtectedAssetAdapter_HTAccess.ss b/templates/SilverStripe/Assets/Flysystem/ProtectedAssetAdapter_HTAccess.ss deleted file mode 100644 index e324f8bb9..000000000 --- a/templates/SilverStripe/Assets/Flysystem/ProtectedAssetAdapter_HTAccess.ss +++ /dev/null @@ -1,2 +0,0 @@ -Deny from all -RewriteRule .* - [F] diff --git a/templates/SilverStripe/Assets/Flysystem/ProtectedAssetAdapter_WebConfig.ss b/templates/SilverStripe/Assets/Flysystem/ProtectedAssetAdapter_WebConfig.ss deleted file mode 100644 index bce81c05b..000000000 --- a/templates/SilverStripe/Assets/Flysystem/ProtectedAssetAdapter_WebConfig.ss +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_HTAccess.ss b/templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_HTAccess.ss deleted file mode 100644 index 54b33a129..000000000 --- a/templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_HTAccess.ss +++ /dev/null @@ -1,27 +0,0 @@ -# -# Whitelist appropriate assets files. -# This file is automatically generated via File.allowed_extensions configuration -# See AssetAdapter::renderTemplate() for reference. -# - - - SetEnv HTTP_MOD_REWRITE On - RewriteEngine On - - # Disable PHP handler - RewriteCond %{REQUEST_URI} .(?i:php|phtml|php3|php4|php5|inc)$ - RewriteRule .* - [F] - - # Allow error pages - RewriteCond %{REQUEST_FILENAME} -f - RewriteRule error[^\\\\/]*\\.html$ - [L] - - # Block invalid file extensions - RewriteCond %{REQUEST_URI} !\\.(?i:<% loop $AllowedExtensions %>$Extension<% if not $Last %>|<% end_if %><% end_loop %>)$ - RewriteRule .* - [F] - - # Non existant files passed to requesthandler - RewriteCond %{REQUEST_URI} ^(.*)$ - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule .* ../framework/main.php?url=%1 [QSA] - diff --git a/templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_WebConfig.ss b/templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_WebConfig.ss deleted file mode 100644 index 18cc41ca8..000000000 --- a/templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_WebConfig.ss +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - <% loop $AllowedExtensions %> - - <% end_loop %> - - - - - - - - - - - - - - - - diff --git a/tests/php/Assets/AssetControlExtensionTest.php b/tests/php/Assets/AssetControlExtensionTest.php deleted file mode 100644 index a9c4c41d2..000000000 --- a/tests/php/Assets/AssetControlExtensionTest.php +++ /dev/null @@ -1,230 +0,0 @@ -logInWithPermission('ADMIN'); - - // Setup fixture manually - $object1 = new AssetControlExtensionTest\VersionedObject(); - $object1->Title = 'My object'; - $fish1 = realpath(__DIR__ .'/../ORM/ImageTest/test-image-high-quality.jpg'); - $object1->Header->setFromLocalFile($fish1, 'Header/MyObjectHeader.jpg'); - $object1->Download->setFromString('file content', 'Documents/File.txt'); - $object1->write(); - $object1->publishSingle(); - - $object2 = new AssetControlExtensionTest\TestObject(); - $object2->Title = 'Unversioned'; - $object2->Image->setFromLocalFile($fish1, 'Images/BeautifulFish.jpg'); - $object2->write(); - - $object3 = new AssetControlExtensionTest\ArchivedObject(); - $object3->Title = 'Archived'; - $object3->Header->setFromLocalFile($fish1, 'Archived/MyObjectHeader.jpg'); - $object3->write(); - $object3->publishSingle(); - } - - public function tearDown() - { - TestAssetStore::reset(); - parent::tearDown(); - } - - public function testFileDelete() - { - Versioned::set_stage(Versioned::DRAFT); - - /** - * @var VersionedObject $object1 - */ - $object1 = AssetControlExtensionTest\VersionedObject::get() - ->filter('Title', 'My object') - ->first(); - /** - * @var Object $object2 - */ - $object2 = AssetControlExtensionTest\TestObject::get() - ->filter('Title', 'Unversioned') - ->first(); - - /** - * @var ArchivedObject $object3 - */ - $object3 = AssetControlExtensionTest\ArchivedObject::get() - ->filter('Title', 'Archived') - ->first(); - - $this->assertTrue($object1->Download->exists()); - $this->assertTrue($object1->Header->exists()); - $this->assertTrue($object2->Image->exists()); - $this->assertTrue($object3->Header->exists()); - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1->Download->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1->Header->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object2->Image->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object3->Header->getVisibility()); - - // Check live stage for versioned objects - $object1Live = Versioned::get_one_by_stage( - VersionedObject::class, - 'Live', - array('"ID"' => $object1->ID) - ); - $object3Live = Versioned::get_one_by_stage( - ArchivedObject::class, - 'Live', - array('"ID"' => $object3->ID) - ); - $this->assertTrue($object1Live->Download->exists()); - $this->assertTrue($object1Live->Header->exists()); - $this->assertTrue($object3Live->Header->exists()); - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1Live->Download->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1Live->Header->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object3Live->Header->getVisibility()); - - // Delete live records; Should cause versioned records to be protected - $object1Live->deleteFromStage('Live'); - $object3Live->deleteFromStage('Live'); - $this->assertTrue($object1->Download->exists()); - $this->assertTrue($object1->Header->exists()); - $this->assertTrue($object3->Header->exists()); - $this->assertTrue($object1Live->Download->exists()); - $this->assertTrue($object1Live->Header->exists()); - $this->assertTrue($object3Live->Header->exists()); - $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object1->Download->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object1->Header->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object3->Header->getVisibility()); - - // Delete draft record; Should remove all records - // Archived assets only should remain - $object1->delete(); - $object2->delete(); - $object3->delete(); - $this->assertFalse($object1->Download->exists()); - $this->assertFalse($object1->Header->exists()); - $this->assertFalse($object2->Image->exists()); - $this->assertTrue($object3->Header->exists()); - $this->assertFalse($object1Live->Download->exists()); - $this->assertFalse($object1Live->Header->exists()); - $this->assertTrue($object3Live->Header->exists()); - $this->assertNull($object1->Download->getVisibility()); - $this->assertNull($object1->Header->getVisibility()); - $this->assertNull($object2->Image->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object3->Header->getVisibility()); - } - - /** - * Test files being replaced - */ - public function testReplaceFile() - { - Versioned::set_stage(Versioned::DRAFT); - - /** - * @var VersionedObject $object1 - */ - $object1 = AssetControlExtensionTest\VersionedObject::get() - ->filter('Title', 'My object') - ->first(); - /** - * @var Object $object2 - */ - $object2 = AssetControlExtensionTest\TestObject::get() - ->filter('Title', 'Unversioned') - ->first(); - - /** - * @var ArchivedObject $object3 - */ - $object3 = AssetControlExtensionTest\ArchivedObject::get() - ->filter('Title', 'Archived') - ->first(); - - $object1TupleOld = $object1->Header->getValue(); - $object2TupleOld = $object2->Image->getValue(); - $object3TupleOld = $object3->Header->getValue(); - - // Replace image and write each to filesystem - $fish1 = realpath(__DIR__ .'/../ORM/ImageTest/test-image-high-quality.jpg'); - $object1->Header->setFromLocalFile($fish1, 'Header/Replaced_MyObjectHeader.jpg'); - $object1->write(); - $object2->Image->setFromLocalFile($fish1, 'Images/Replaced_BeautifulFish.jpg'); - $object2->write(); - $object3->Header->setFromLocalFile($fish1, 'Archived/Replaced_MyObjectHeader.jpg'); - $object3->write(); - - // Check that old published records are left public, but removed for unversioned object2 - $this->assertEquals( - AssetStore::VISIBILITY_PUBLIC, - $this->getAssetStore()->getVisibility($object1TupleOld['Filename'], $object1TupleOld['Hash']) - ); - $this->assertEquals( - null, // Old file is destroyed - $this->getAssetStore()->getVisibility($object2TupleOld['Filename'], $object2TupleOld['Hash']) - ); - $this->assertEquals( - AssetStore::VISIBILITY_PUBLIC, - $this->getAssetStore()->getVisibility($object3TupleOld['Filename'], $object3TupleOld['Hash']) - ); - - // Check that visibility of new file is correct - // Note that $object2 has no canView() is true, so assets end up public - $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object1->Header->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object2->Image->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PROTECTED, $object3->Header->getVisibility()); - - // Publish changes to versioned records - $object1->publishSingle(); - $object3->publishSingle(); - - // After publishing, old object1 is deleted, but since object3 has archiving enabled, - // the orphaned file is intentionally left in the protected store - $this->assertEquals( - null, - $this->getAssetStore()->getVisibility($object1TupleOld['Filename'], $object1TupleOld['Hash']) - ); - $this->assertEquals( - AssetStore::VISIBILITY_PROTECTED, - $this->getAssetStore()->getVisibility($object3TupleOld['Filename'], $object3TupleOld['Hash']) - ); - - // And after publish, all files are public - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object1->Header->getVisibility()); - $this->assertEquals(AssetStore::VISIBILITY_PUBLIC, $object3->Header->getVisibility()); - } - - /** - * @return AssetStore - */ - protected function getAssetStore() - { - return Injector::inst()->get('AssetStore'); - } -} diff --git a/tests/php/Assets/AssetControlExtensionTest/ArchivedObject.php b/tests/php/Assets/AssetControlExtensionTest/ArchivedObject.php deleted file mode 100644 index 1e8b1fda2..000000000 --- a/tests/php/Assets/AssetControlExtensionTest/ArchivedObject.php +++ /dev/null @@ -1,15 +0,0 @@ - 'Varchar(255)', - 'Image' => "DBFile('image/supported')" - ); - - private static $table_name = 'AssetControlExtensionTest_TestObject'; - - /** - * @param Member $member - * @return bool - */ - public function canView($member = null) - { - return true; - } -} diff --git a/tests/php/Assets/AssetControlExtensionTest/VersionedObject.php b/tests/php/Assets/AssetControlExtensionTest/VersionedObject.php deleted file mode 100644 index e580920d1..000000000 --- a/tests/php/Assets/AssetControlExtensionTest/VersionedObject.php +++ /dev/null @@ -1,52 +0,0 @@ - 'Varchar(255)', - 'Header' => "DBFile('image/supported')", - 'Download' => 'DBFile' - ); - - private static $table_name = 'AssetControlExtensionTest_VersionedObject'; - - /** - * @param Member $member - * @return bool - */ - public function canView($member = null) - { - if (!$member) { - $member = Member::currentUser(); - } - - // Expectation that versioned::canView will hide this object in draft - $result = $this->extendedCan('canView', $member); - if ($result !== null) { - return $result; - } - - // Open to public - return true; - } -} diff --git a/tests/php/Assets/AssetManipulationListTest.php b/tests/php/Assets/AssetManipulationListTest.php deleted file mode 100644 index 45d6dc88b..000000000 --- a/tests/php/Assets/AssetManipulationListTest.php +++ /dev/null @@ -1,86 +0,0 @@ - 'Test1.jpg', 'Hash' => '975677589962604d9e16b700cf84734f9dda2817']; - $file2 = ['Filename' => 'Test2.jpg', 'Hash' => '22af86a45ea56287437a12cf83aded5c077a5db5']; - $file3 = ['Filename' => 'DupeHash1.jpg', 'Hash' => 'f167433dd318e738281b845a07d7be2053b8c997']; - $file4 = ['Filename' => 'DupeName.jpg', 'Hash' => 'afde6577a034323959b7915f41ac8d1f53bc597f']; - $file5 = ['Filename' => 'DupeName.jpg', 'Hash' => '1e94b066e5aa16907d0e5e32556c7a2a0b692eb9']; - $file6 = ['Filename' => 'DupeHash2.jpg', 'Hash' => 'f167433dd318e738281b845a07d7be2053b8c997']; - - // Non-overlapping assets remain in assigned sets - $this->assertTrue($set->addDeletedAsset($file1)); - $this->assertTrue($set->addDeletedAsset($file2)); - $this->assertTrue($set->addProtectedAsset($file3)); - $this->assertTrue($set->addProtectedAsset($file4)); - $this->assertTrue($set->addPublicAsset($file5)); - $this->assertTrue($set->addPublicAsset($file6)); - - // Check initial state of list - $this->assertEquals(6, $this->countItems($set)); - $this->assertContains($file1, $set->getDeletedAssets()); - $this->assertContains($file2, $set->getDeletedAssets()); - $this->assertContains($file3, $set->getProtectedAssets()); - $this->assertContains($file4, $set->getProtectedAssets()); - $this->assertContains($file5, $set->getPublicAssets()); - $this->assertContains($file6, $set->getPublicAssets()); - - // Public or Protected assets will not be deleted - $this->assertFalse($set->addDeletedAsset($file3)); - $this->assertFalse($set->addDeletedAsset($file4)); - $this->assertFalse($set->addDeletedAsset($file5)); - $this->assertFalse($set->addDeletedAsset($file6)); - $this->assertEquals(6, $this->countItems($set)); - $this->assertNotContains($file3, $set->getDeletedAssets()); - $this->assertNotContains($file4, $set->getDeletedAssets()); - $this->assertNotContains($file5, $set->getDeletedAssets()); - $this->assertNotContains($file6, $set->getDeletedAssets()); - - // Adding records as protected will remove them from the deletion list, but - // not the public list - $this->assertTrue($set->addProtectedAsset($file1)); - $this->assertFalse($set->addProtectedAsset($file5)); - $this->assertEquals(6, $this->countItems($set)); - $this->assertNotContains($file1, $set->getDeletedAssets()); - $this->assertContains($file1, $set->getProtectedAssets()); - $this->assertNotContains($file5, $set->getProtectedAssets()); - $this->assertContains($file5, $set->getPublicAssets()); - - // Adding records as public will ensure they are not deleted or marked as protected - // Existing public assets won't be re-added - $this->assertTrue($set->addPublicAsset($file2)); - $this->assertTrue($set->addPublicAsset($file4)); - $this->assertFalse($set->addPublicAsset($file5)); - $this->assertEquals(6, $this->countItems($set)); - $this->assertNotContains($file2, $set->getDeletedAssets()); - $this->assertNotContains($file2, $set->getProtectedAssets()); - $this->assertContains($file2, $set->getPublicAssets()); - $this->assertNotContains($file4, $set->getProtectedAssets()); - $this->assertContains($file4, $set->getPublicAssets()); - $this->assertContains($file5, $set->getPublicAssets()); - } - - /** - * Helper to count all items in a set - * - * @param AssetManipulationList $set - * @return int - */ - protected function countItems(AssetManipulationList $set) - { - return count($set->getPublicAssets()) + count($set->getProtectedAssets()) + count($set->getDeletedAssets()); - } -} diff --git a/tests/php/Assets/FileFinderTest.php b/tests/php/Assets/FileFinderTest.php deleted file mode 100644 index 030e51dfa..000000000 --- a/tests/php/Assets/FileFinderTest.php +++ /dev/null @@ -1,136 +0,0 @@ -base = __DIR__ . '/FileFinderTest'; - parent::__construct(); - } - - public function testBasicOperation() - { - $this->assertFinderFinds( - new FileFinder(), - array( - 'file1.txt', - 'file2.txt', - 'dir1/dir1file1.txt', - 'dir1/dir1file2.txt', - 'dir1/dir2/dir2file1.txt', - 'dir1/dir2/dir3/dir3file1.txt' - ) - ); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testInvalidOptionThrowsException() - { - $finder = new FileFinder(); - $finder->setOption('this_doesnt_exist', 'ok'); - } - - public function testFilenameRegex() - { - $finder = new FileFinder(); - $finder->setOption('name_regex', '/file2\.txt$/'); - - $this->assertFinderFinds( - $finder, - array( - 'file2.txt', - 'dir1/dir1file2.txt'), - 'The finder only returns files matching the name regex.' - ); - } - - public function testIgnoreFiles() - { - $finder = new FileFinder(); - $finder->setOption('ignore_files', array('file1.txt', 'dir1file1.txt', 'dir2file1.txt')); - - $this->assertFinderFinds( - $finder, - array( - 'file2.txt', - 'dir1/dir1file2.txt', - 'dir1/dir2/dir3/dir3file1.txt'), - 'The finder ignores files with the basename in the ignore_files setting.' - ); - } - - public function testIgnoreDirs() - { - $finder = new FileFinder(); - $finder->setOption('ignore_dirs', array('dir2')); - - $this->assertFinderFinds( - $finder, - array( - 'file1.txt', - 'file2.txt', - 'dir1/dir1file1.txt', - 'dir1/dir1file2.txt'), - 'The finder ignores directories in ignore_dirs.' - ); - } - - public function testMinDepth() - { - $finder = new FileFinder(); - $finder->setOption('min_depth', 2); - - $this->assertFinderFinds( - $finder, - array( - 'dir1/dir2/dir2file1.txt', - 'dir1/dir2/dir3/dir3file1.txt' - ), - 'The finder respects the min depth setting.' - ); - } - - public function testMaxDepth() - { - $finder = new FileFinder(); - $finder->setOption('max_depth', 1); - - $this->assertFinderFinds( - $finder, - array( - 'file1.txt', - 'file2.txt', - 'dir1/dir1file1.txt', - 'dir1/dir1file2.txt'), - 'The finder respects the max depth setting.' - ); - } - - public function assertFinderFinds(FileFinder $finder, $expect, $message = null) - { - $found = $finder->find($this->base); - - foreach ($expect as $k => $file) { - $expect[$k] = "{$this->base}/$file"; - } - - sort($expect); - sort($found); - - $this->assertEquals($expect, $found, $message); - } -} diff --git a/tests/php/Assets/FileFinderTest/dir1/dir1file1.txt b/tests/php/Assets/FileFinderTest/dir1/dir1file1.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/php/Assets/FileFinderTest/dir1/dir1file2.txt b/tests/php/Assets/FileFinderTest/dir1/dir1file2.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/php/Assets/FileFinderTest/dir1/dir2/dir2file1.txt b/tests/php/Assets/FileFinderTest/dir1/dir2/dir2file1.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/php/Assets/FileFinderTest/dir1/dir2/dir3/dir3file1.txt b/tests/php/Assets/FileFinderTest/dir1/dir2/dir3/dir3file1.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/php/Assets/FileFinderTest/file1.txt b/tests/php/Assets/FileFinderTest/file1.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/php/Assets/FileFinderTest/file2.txt b/tests/php/Assets/FileFinderTest/file2.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/php/Assets/FileMigrationHelperTest.php b/tests/php/Assets/FileMigrationHelperTest.php deleted file mode 100644 index ed7eba7dc..000000000 --- a/tests/php/Assets/FileMigrationHelperTest.php +++ /dev/null @@ -1,101 +0,0 @@ - array( - 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'; - } - - - public function setUp() - { - Config::nest(); // additional nesting here necessary - Config::inst()->update(File::class, 'migrate_legacy_file', false); - parent::setUp(); - - // Set backend root to /FileMigrationHelperTest/assets - TestAssetStore::activate('FileMigrationHelperTest/assets'); - - // Ensure that each file has a local record file in this new assets base - $from = FRAMEWORK_PATH . '/tests/php/ORM/ImageTest/test-image-low-quality.jpg'; - foreach (File::get()->exclude('ClassName', Folder::class) as $file) { - $dest = TestAssetStore::base_path() . '/' . $file->generateFilename(); - Filesystem::makeFolder(dirname($dest)); - copy($from, $dest); - } - } - - public function tearDown() - { - TestAssetStore::reset(); - Filesystem::removeFolder($this->getBasePath()); - parent::tearDown(); - Config::unnest(); - } - - /** - * Test file migration - */ - public function testMigration() - { - // 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"); - } - - // Do migration - $helper = new FileMigrationHelper(); - $result = $helper->run($this->getBasePath()); - $this->assertEquals(5, $result); - - // Test that each file exists - foreach (File::get()->exclude('ClassName', Folder::class) as $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"); - $this->assertEquals( - '33be1b95cba0358fe54e8b13532162d52f97421c', - $file->File->getHash(), - "File with name {$filename} has the correct hash" - ); - $this->assertTrue($file->isPublished(), "File is published after migration"); - } - } -} diff --git a/tests/php/Assets/FileMigrationHelperTest.yml b/tests/php/Assets/FileMigrationHelperTest.yml deleted file mode 100644 index 53e6e00b0..000000000 --- a/tests/php/Assets/FileMigrationHelperTest.yml +++ /dev/null @@ -1,21 +0,0 @@ -SilverStripe\Assets\Folder: - parent: - Name: ParentFolder - subfolder: - Name: SubFolder - Parent: =>SilverStripe\Assets\Folder.parent -SilverStripe\Assets\Image: - image1: - Name: myimage.jpg - image2: - Name: myimage.jpg - ParentID: =>SilverStripe\Assets\Folder.subfolder -SilverStripe\Assets\File: - file1: - Name: anotherfile.jpg - file2: - Name: file.jpg - ParentID: =>SilverStripe\Assets\Folder.parent - file3: - Name: picture.jpg - ParentID: =>SilverStripe\Assets\Folder.subfolder diff --git a/tests/php/Assets/FileMigrationHelperTest/Extension.php b/tests/php/Assets/FileMigrationHelperTest/Extension.php deleted file mode 100644 index 32c005000..000000000 --- a/tests/php/Assets/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/Assets/FileNameFilterTest.php b/tests/php/Assets/FileNameFilterTest.php deleted file mode 100644 index bd4261f27..000000000 --- a/tests/php/Assets/FileNameFilterTest.php +++ /dev/null @@ -1,152 +0,0 @@ -update( - 'SilverStripe\\Assets\\FileNameFilter', - 'default_replacements', - array( - '/\s/' => '-', // remove whitespace - '/_/' => '-', // underscores to dashes - '/[^A-Za-z0-9+.\-]+/' => '', // remove non-ASCII chars, only allow alphanumeric plus dash and dot - '/[\-]{2,}/' => '-', // remove duplicate dashes - '/^[\.\-_]+/' => '', // Remove all leading dots, dashes or underscores - ) - ); - } - - public function testFilter() - { - $name = 'Brötchen für allë-mit_Unterstrich!.jpg'; - $filter = new FileNameFilter(); - $filter->setTransliterator(false); - $this->assertEquals( - 'Brtchen-fr-all-mit-Unterstrich.jpg', - $filter->filter($name) - ); - } - - public function testFilterWithTransliterator() - { - $name = 'Brötchen für allë-mit_Unterstrich!.jpg'; - $filter = new FileNameFilter(); - $filter->setTransliterator(new Transliterator()); - $this->assertEquals( - 'Broetchen-fuer-alle-mit-Unterstrich.jpg', - $filter->filter($name) - ); - } - - public function testFilterWithCustomRules() - { - $name = 'Kuchen ist besser.jpg'; - $filter = new FileNameFilter(); - $filter->setTransliterator(false); - $filter->setReplacements(array('/[\s-]/' => '_')); - $this->assertEquals( - 'Kuchen_ist_besser.jpg', - $filter->filter($name) - ); - } - - public function testFilterWithEmptyString() - { - $name = 'ö ö ö.jpg'; - $filter = new FileNameFilter(); - $filter->setTransliterator(new Transliterator()); - $result = $filter->filter($name); - $this->assertFalse( - empty($result) - ); - $this->assertStringEndsWith( - '.jpg', - $result - ); - $this->assertGreaterThan( - strlen('.jpg'), - strlen($result) - ); - } - - public function testUnderscoresStartOfNameRemoved() - { - $name = '_test.txt'; - $filter = new FileNameFilter(); - $this->assertEquals('test.txt', $filter->filter($name)); - } - - public function testDoubleUnderscoresStartOfNameRemoved() - { - $name = '__test.txt'; - $filter = new FileNameFilter(); - $this->assertEquals('test.txt', $filter->filter($name)); - } - - public function testDotsStartOfNameRemoved() - { - $name = '.test.txt'; - $filter = new FileNameFilter(); - $this->assertEquals('test.txt', $filter->filter($name)); - } - - public function testDoubleDotsStartOfNameRemoved() - { - $name = '..test.txt'; - $filter = new FileNameFilter(); - $this->assertEquals('test.txt', $filter->filter($name)); - } - - public function testMixedInvalidCharsStartOfNameRemoved() - { - $name = '..#@$#@$^__test.txt'; - $filter = new FileNameFilter(); - $this->assertEquals('test.txt', $filter->filter($name)); - } - - public function testWhitespaceRemoved() - { - $name = ' test doc.txt'; - $filter = new FileNameFilter(); - $this->assertEquals('test-doc.txt', $filter->filter($name)); - } - - public function testUnderscoresReplacedWithDashes() - { - $name = 'test_doc.txt'; - $filter = new FileNameFilter(); - $this->assertEquals('test-doc.txt', $filter->filter($name)); - } - - public function testNonAsciiCharsReplacedWithDashes() - { - $name = '!@#$%^test_123@##@$#%^.txt'; - $filter = new FileNameFilter(); - $this->assertEquals('test-123.txt', $filter->filter($name)); - } - - public function testDuplicateDashesRemoved() - { - $name = 'test--document.txt'; - $filter = new FileNameFilter(); - $this->assertEquals('test-document.txt', $filter->filter($name)); - } - - public function testDoesntAddExtensionWhenMissing() - { - $name = 'no-extension'; - $filter = new FileNameFilter(); - $this->assertEquals('no-extension', $filter->filter($name)); - } -} diff --git a/tests/php/Assets/FileTest.php b/tests/php/Assets/FileTest.php deleted file mode 100644 index b38e6770c..000000000 --- a/tests/php/Assets/FileTest.php +++ /dev/null @@ -1,714 +0,0 @@ -logInWithPermission('ADMIN'); - Versioned::set_stage(Versioned::DRAFT); - - // Set backend root to /ImageTest - TestAssetStore::activate('FileTest'); - - // Create a test folders for each of the fixture references - $folderIDs = $this->allFixtureIDs(Folder::class); - foreach ($folderIDs as $folderID) { - $folder = DataObject::get_by_id(Folder::class, $folderID); - $filePath = ASSETS_PATH . '/FileTest/' . $folder->getFilename(); - Filesystem::makeFolder($filePath); - } - - // Create a test files for each of the fixture references - $fileIDs = $this->allFixtureIDs(File::class); - foreach ($fileIDs as $fileID) { - /** - * @var File $file - */ - $file = DataObject::get_by_id(File::class, $fileID); - $root = ASSETS_PATH . '/FileTest/'; - if ($folder = $file->Parent()) { - $root .= $folder->getFilename(); - } - $path = $root . substr($file->getHash(), 0, 10) . '/' . basename($file->getFilename()); - Filesystem::makeFolder(dirname($path)); - $fh = fopen($path, "w+"); - fwrite($fh, str_repeat('x', 1000000)); - fclose($fh); - } - - // Conditional fixture creation in case the 'cms' module is installed - if (class_exists('SilverStripe\\CMS\\Model\\ErrorPage')) { - $page = new ErrorPage( - array( - 'Title' => 'Page not Found', - 'ErrorCode' => 404 - ) - ); - $page->write(); - $page->copyVersionToStage(Versioned::DRAFT, Versioned::LIVE); - } - } - - public function tearDown() - { - TestAssetStore::reset(); - parent::tearDown(); - } - - public function testLinkShortcodeHandler() - { - $testFile = $this->objFromFixture(File::class, 'asdf'); - - $parser = new ShortcodeParser(); - $parser->register('file_link', array(File::class, 'handle_shortcode')); - - $fileShortcode = sprintf('[file_link,id=%d]', $testFile->ID); - $fileEnclosed = sprintf('[file_link,id=%d]Example Content[/file_link]', $testFile->ID); - - $fileShortcodeExpected = $testFile->Link(); - $fileEnclosedExpected = sprintf( - '
    Example Content', - $testFile->Link() - ); - - $this->assertEquals($fileShortcodeExpected, $parser->parse($fileShortcode), 'Test that simple linking works.'); - $this->assertEquals($fileEnclosedExpected, $parser->parse($fileEnclosed), 'Test enclosed content is linked.'); - - $testFile->delete(); - - $fileShortcode = '[file_link,id="-1"]'; - $fileEnclosed = '[file_link,id="-1"]Example Content[/file_link]'; - - $this->assertEquals('', $parser->parse('[file_link]'), 'Test that invalid ID attributes are not parsed.'); - $this->assertEquals('', $parser->parse('[file_link,id="text"]')); - $this->assertEquals('', $parser->parse('[file_link]Example Content[/file_link]')); - - if (class_exists('SilverStripe\\CMS\\Model\\ErrorPage')) { - $errorPage = ErrorPage::get()->filter('ErrorCode', 404)->first(); - $this->assertEquals( - $errorPage->Link(), - $parser->parse($fileShortcode), - 'Test link to 404 page if no suitable matches.' - ); - $this->assertEquals( - sprintf('Example Content', $errorPage->Link()), - $parser->parse($fileEnclosed) - ); - } else { - $this->assertEquals( - '', - $parser->parse($fileShortcode), - 'Short code is removed if file record is not present.' - ); - $this->assertEquals('', $parser->parse($fileEnclosed)); - } - } - - public function testCreateWithFilenameWithSubfolder() - { - // Note: We can't use fixtures/setUp() for this, as we want to create the db record manually. - // Creating the folder is necessary to avoid having "Filename" overwritten by setName()/setRelativePath(), - // because the parent folders don't exist in the database - $folder = Folder::find_or_make('/FileTest/'); - $testfilePath = BASE_PATH . '/assets/FileTest/CreateWithFilenameHasCorrectPath.txt'; // Important: No leading slash - $fh = fopen($testfilePath, 'w'); - fwrite($fh, str_repeat('x', 1000000)); - fclose($fh); - - $file = new File(); - $file->setFromLocalFile($testfilePath); - $file->ParentID = $folder->ID; - $file->write(); - - $this->assertEquals( - 'CreateWithFilenameHasCorrectPath.txt', - $file->Name, - '"Name" property is automatically set from "Filename"' - ); - $this->assertEquals( - 'FileTest/CreateWithFilenameHasCorrectPath.txt', - $file->Filename, - '"Filename" property remains unchanged' - ); - - // TODO This should be auto-detected, see File->updateFilesystem() - // $this->assertInstanceOf('Folder', $file->Parent(), 'Parent folder is created in database'); - // $this->assertFileExists($file->Parent()->getURL(), 'Parent folder is created on filesystem'); - // $this->assertEquals('FileTest', $file->Parent()->Name); - // $this->assertInstanceOf('Folder', $file->Parent()->Parent(), 'Grandparent folder is created in database'); - // $this->assertFileExists($file->Parent()->Parent()->getURL(), - // 'Grandparent folder is created on filesystem'); - // $this->assertEquals('assets', $file->Parent()->Parent()->Name); - } - - public function testGetExtension() - { - $this->assertEquals( - '', - File::get_file_extension('myfile'), - 'No extension' - ); - $this->assertEquals( - 'txt', - File::get_file_extension('myfile.txt'), - 'Simple extension' - ); - $this->assertEquals( - 'gz', - File::get_file_extension('myfile.tar.gz'), - 'Double-barrelled extension only returns last bit' - ); - } - - public function testValidateExtension() - { - Session::set('loggedInAs', null); - - $orig = Config::inst()->get(File::class, 'allowed_extensions'); - Config::inst()->remove(File::class, 'allowed_extensions'); - Config::inst()->update(File::class, 'allowed_extensions', array('txt')); - - $file = $this->objFromFixture(File::class, 'asdf'); - - // Invalid ext - $file->Name = 'asdf.php'; - $result = $file->validate(); - $this->assertFalse($result->isValid()); - $messages = $result->getMessages(); - $this->assertEquals(1, count($messages)); - $this->assertEquals('Extension is not allowed', $messages[0]['message']); - - // Valid ext - $file->Name = 'asdf.txt'; - $result = $file->validate(); - $this->assertTrue($result->isValid()); - - // Capital extension is valid as well - $file->Name = 'asdf.TXT'; - $result = $file->validate(); - $this->assertTrue($result->isValid()); - } - - public function testAppCategory() - { - // Test various categories - $this->assertEquals('image', File::get_app_category('jpg')); - $this->assertEquals('image', File::get_app_category('JPG')); - $this->assertEquals('image', File::get_app_category('JPEG')); - $this->assertEquals('image', File::get_app_category('png')); - $this->assertEquals('image', File::get_app_category('tif')); - $this->assertEquals('document', File::get_app_category('pdf')); - $this->assertEquals('video', File::get_app_category('mov')); - $this->assertEquals('audio', File::get_app_category('OGG')); - } - - public function testGetCategoryExtensions() - { - // Test specific categories - $images = array( - 'alpha', 'als', 'bmp', 'cel', 'gif', 'ico', 'icon', 'jpeg', 'jpg', 'pcx', 'png', 'ps', 'tif', 'tiff' - ); - $this->assertEquals($images, File::get_category_extensions('image')); - $this->assertEquals(array('gif', 'jpeg', 'jpg', 'png'), File::get_category_extensions('image/supported')); - $this->assertEquals($images, File::get_category_extensions(array('image', 'image/supported'))); - $this->assertEquals( - array('fla', 'gif', 'jpeg', 'jpg', 'png', 'swf'), - File::get_category_extensions(array('flash', 'image/supported')) - ); - - // Test other categories have at least one item - $this->assertNotEmpty(File::get_category_extensions('archive')); - $this->assertNotEmpty(File::get_category_extensions('audio')); - $this->assertNotEmpty(File::get_category_extensions('document')); - $this->assertNotEmpty(File::get_category_extensions('flash')); - $this->assertNotEmpty(File::get_category_extensions('video')); - } - - /** - * @dataProvider allowedExtensions - * @param string $extension - */ - public function testAllFilesHaveCategory($extension) - { - $this->assertNotEmpty( - File::get_app_category($extension), - "Assert that extension {$extension} has a valid category" - ); - } - - /** - * Gets the list of all extensions for testing - * - * @return array - */ - public function allowedExtensions() - { - $args = array(); - foreach (array_filter(File::config()->allowed_extensions) as $ext) { - $args[] = array($ext); - } - return $args; - } - - public function testSetNameChangesFilesystemOnWrite() - { - /** - * @var File $file - */ - $file = $this->objFromFixture(File::class, 'asdf'); - $this->logInWithPermission('ADMIN'); - $file->publishRecursive(); - $oldTuple = $file->File->getValue(); - - // Rename - $file->Name = 'renamed.txt'; - $newTuple = $oldTuple; - $newTuple['Filename'] = $file->generateFilename(); - - // Before write() - $this->assertTrue( - $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']), - 'Old path is still present' - ); - $this->assertFalse( - $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']), - 'New path is updated in memory, not written before write() is called' - ); - - // After write() - $file->write(); - $this->assertTrue( - $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']), - 'Old path exists after draft change' - ); - $this->assertTrue( - $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']), - 'New path is created after write()' - ); - - // After publish - $file->publishRecursive(); - $this->assertFalse( - $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']), - 'Old file is finally removed after publishing new file' - ); - $this->assertTrue( - $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']), - 'New path is created after write()' - ); - } - - public function testSetParentIDChangesFilesystemOnWrite() - { - $file = $this->objFromFixture(File::class, 'asdf'); - $this->logInWithPermission('ADMIN'); - $file->publishRecursive(); - $subfolder = $this->objFromFixture(Folder::class, 'subfolder'); - $oldTuple = $file->File->getValue(); - - // set ParentID - $file->ParentID = $subfolder->ID; - $newTuple = $oldTuple; - $newTuple['Filename'] = $file->generateFilename(); - - // Before write() - $this->assertTrue( - $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']), - 'Old path is still present' - ); - $this->assertFalse( - $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']), - 'New path is updated in memory, not written before write() is called' - ); - $file->write(); - - // After write() - $file->write(); - $this->assertTrue( - $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']), - 'Old path exists after draft change' - ); - $this->assertTrue( - $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']), - 'New path is created after write()' - ); - - // After publish - $file->publishSingle(); - $this->assertFalse( - $this->getAssetStore()->exists($oldTuple['Filename'], $oldTuple['Hash']), - 'Old file is finally removed after publishing new file' - ); - $this->assertTrue( - $this->getAssetStore()->exists($newTuple['Filename'], $newTuple['Hash']), - 'New path is created after write()' - ); - } - - /** - * @see http://open.silverstripe.org/ticket/5693 - */ - public function testSetNameWithInvalidExtensionDoesntChangeFilesystem() - { - Config::inst()->remove(File::class, 'allowed_extensions'); - Config::inst()->update(File::class, 'allowed_extensions', array('txt')); - $this->setExpectedException(ValidationException::class); - - $file = $this->objFromFixture(File::class, 'asdf'); - $file->Name = 'renamed.php'; // evil extension - $file->write(); - } - - public function testGetURL() - { - $rootfile = $this->objFromFixture(File::class, 'asdf'); - $this->assertEquals('/assets/FileTest/55b443b601/FileTest.txt', $rootfile->getURL()); - } - - public function testGetAbsoluteURL() - { - $rootfile = $this->objFromFixture(File::class, 'asdf'); - $this->assertEquals( - Director::absoluteBaseURL() . 'assets/FileTest/55b443b601/FileTest.txt', - $rootfile->getAbsoluteURL() - ); - } - - public function testNameAndTitleGeneration() - { - // When name is assigned, title is automatically assigned - $file = $this->objFromFixture(Image::class, 'setfromname'); - $this->assertEquals('FileTest', $file->Title); - } - - public function testSizeAndAbsoluteSizeParameters() - { - $file = $this->objFromFixture(File::class, 'asdf'); - - /* AbsoluteSize will give the integer number */ - $this->assertEquals(1000000, $file->AbsoluteSize); - /* Size will give a humanised number */ - $this->assertEquals('977 KB', $file->Size); - } - - public function testFileType() - { - $file = $this->objFromFixture(Image::class, 'gif'); - $this->assertEquals("GIF image - good for diagrams", $file->FileType); - - $file = $this->objFromFixture(File::class, 'pdf'); - $this->assertEquals("Adobe Acrobat PDF file", $file->FileType); - - $file = $this->objFromFixture(Image::class, 'gifupper'); - $this->assertEquals("GIF image - good for diagrams", $file->FileType); - - /* Only a few file types are given special descriptions; the rest are unknown */ - $file = $this->objFromFixture(File::class, 'asdf'); - $this->assertEquals("unknown", $file->FileType); - } - - /** - * Test the File::format_size() method - */ - public function testFormatSize() - { - $this->assertEquals("1000 bytes", File::format_size(1000)); - $this->assertEquals("1023 bytes", File::format_size(1023)); - $this->assertEquals("1 KB", File::format_size(1025)); - $this->assertEquals("9.8 KB", File::format_size(10000)); - $this->assertEquals("49 KB", File::format_size(50000)); - $this->assertEquals("977 KB", File::format_size(1000000)); - $this->assertEquals("1 MB", File::format_size(1024*1024)); - $this->assertEquals("954 MB", File::format_size(1000000000)); - $this->assertEquals("1 GB", File::format_size(1024*1024*1024)); - $this->assertEquals("9.3 GB", File::format_size(10000000000)); - // It use any denomination higher than GB. It also doesn't overflow with >32 bit integers - $this->assertEquals("93132.3 GB", File::format_size(100000000000000)); - } - - public function testDeleteFile() - { - /** - * @var File $file - */ - $file = $this->objFromFixture(File::class, 'asdf'); - $this->logInWithPermission('ADMIN'); - $file->publishSingle(); - $tuple = $file->File->getValue(); - - // Before delete - $this->assertTrue( - $this->getAssetStore()->exists($tuple['Filename'], $tuple['Hash']), - 'File is still present' - ); - - // after unpublish - $file->doUnpublish(); - $this->assertTrue( - $this->getAssetStore()->exists($tuple['Filename'], $tuple['Hash']), - 'File is still present after unpublish' - ); - - // after delete - $file->delete(); - $this->assertFalse( - $this->getAssetStore()->exists($tuple['Filename'], $tuple['Hash']), - 'File is deleted after unpublish and delete' - ); - } - - public function testRenameFolder() - { - $newTitle = "FileTest-folder-renamed"; - - //rename a folder's title - $folderID = $this->objFromFixture(Folder::class, "folder2")->ID; - $folder = DataObject::get_by_id(Folder::class, $folderID); - $folder->Title = $newTitle; - $folder->write(); - - //get folder again and see if the filename has changed - $folder = DataObject::get_by_id(Folder::class, $folderID); - $this->assertEquals( - $newTitle . '/', - $folder->Filename, - "Folder Filename updated after rename of Title" - ); - - //rename a folder's name - $newTitle2 = "FileTest-folder-renamed2"; - $folder->Name = $newTitle2; - $folder->write(); - - //get folder again and see if the Title has changed - $folder = DataObject::get_by_id(Folder::class, $folderID); - $this->assertEquals( - $folder->Title, - $newTitle2, - "Folder Title updated after rename of Name" - ); - - - //rename a folder's Filename - $newTitle3 = "FileTest-folder-renamed3"; - $folder->Filename = $newTitle3; - $folder->write(); - - //get folder again and see if the Title has changed - $folder = DataObject::get_by_id(Folder::class, $folderID); - $this->assertEquals( - $folder->Title, - $newTitle3, - "Folder Title updated after rename of Filename" - ); - } - - public function testRenamesDuplicateFilesInSameFolder() - { - $original = new File(); - $original->update([ - 'Name' => 'file1.txt', - 'ParentID' => 0 - ]); - $original->write(); - - $duplicate = new File(); - $duplicate->update([ - 'Name' => 'file1.txt', - 'ParentID' => 0 - ]); - $duplicate->write(); - - $original = File::get()->byID($original->ID); - - $this->assertEquals($original->Name, 'file1.txt'); - $this->assertEquals($original->Title, 'file1'); - $this->assertEquals($duplicate->Name, 'file1-v2.txt'); - $this->assertEquals($duplicate->Title, 'file1 v2'); - } - - public function testSetsEmptyTitleToNameWithoutExtensionAndSpecialCharacters() - { - $fileWithTitle = new File(); - $fileWithTitle->update([ - 'Name' => 'file1-with-title.txt', - 'Title' => 'Some Title' - ]); - $fileWithTitle->write(); - - $this->assertEquals($fileWithTitle->Name, 'file1-with-title.txt'); - $this->assertEquals($fileWithTitle->Title, 'Some Title'); - - $fileWithoutTitle = new File(); - $fileWithoutTitle->update([ - 'Name' => 'file1-without-title.txt', - ]); - $fileWithoutTitle->write(); - - $this->assertEquals($fileWithoutTitle->Name, 'file1-without-title.txt'); - $this->assertEquals($fileWithoutTitle->Title, 'file1 without title'); - } - - public function testSetsEmptyNameToSingularNameWithoutTitle() - { - $fileWithTitle = new File(); - $fileWithTitle->update([ - 'Name' => '', - 'Title' => 'Some Title', - ]); - $fileWithTitle->write(); - - $this->assertEquals($fileWithTitle->Name, 'Some-Title'); - $this->assertEquals($fileWithTitle->Title, 'Some Title'); - - $fileWithoutTitle = new File(); - $fileWithoutTitle->update([ - 'Name' => '', - 'Title' => '', - ]); - $fileWithoutTitle->write(); - - $this->assertEquals($fileWithoutTitle->Name, $fileWithoutTitle->i18n_singular_name()); - $this->assertEquals($fileWithoutTitle->Title, $fileWithoutTitle->i18n_singular_name()); - } - - public function testSetsEmptyNameToTitleIfPresent() - { - $file = new File(); - $file->update([ - 'Name' => '', - 'Title' => 'file1', - ]); - $file->write(); - - $this->assertEquals($file->Name, 'file1'); - $this->assertEquals($file->Title, 'file1'); - } - - public function testSetsOwnerOnFirstWrite() - { - Session::set('loggedInAs', null); - $member1 = new Member(); - $member1->write(); - $member2 = new Member(); - $member2->write(); - - $file1 = new File(); - $file1->write(); - $this->assertEquals(0, $file1->OwnerID, 'Owner not written when no user is logged in'); - - $member1->logIn(); - $file2 = new File(); - $file2->write(); - $this->assertEquals($member1->ID, $file2->OwnerID, 'Owner written when user is logged in'); - - $member2->logIn(); - $file2->forceChange(); - $file2->write(); - $this->assertEquals($member1->ID, $file2->OwnerID, 'Owner not overwritten on existing files'); - } - - public function testCanEdit() - { - $file = $this->objFromFixture(Image::class, 'gif'); - - // Test anonymous permissions - Session::set('loggedInAs', null); - $this->assertFalse($file->canEdit(), "Anonymous users can't edit files"); - - // Test permissionless user - $this->objFromFixture(Member::class, 'frontend')->logIn(); - $this->assertFalse($file->canEdit(), "Permissionless users can't edit files"); - - // Test global CMS section users - $this->objFromFixture(Member::class, 'cms')->logIn(); - $this->assertTrue($file->canEdit(), "Users with all CMS section access can edit files"); - - // Test cms access users without file access - $this->objFromFixture(Member::class, 'security')->logIn(); - $this->assertFalse($file->canEdit(), "Security CMS users can't edit files"); - - // Test asset-admin user - $this->objFromFixture(Member::class, 'assetadmin')->logIn(); - $this->assertTrue($file->canEdit(), "Asset admin users can edit files"); - - // Test admin - $this->objFromFixture(Member::class, 'admin')->logIn(); - $this->assertTrue($file->canEdit(), "Admins can edit files"); - } - - public function testJoinPaths() - { - $this->assertEquals('name/file.jpg', File::join_paths('/name', 'file.jpg')); - $this->assertEquals('name/file.jpg', File::join_paths('name', 'file.jpg')); - $this->assertEquals('name/file.jpg', File::join_paths('/name', '/file.jpg')); - $this->assertEquals('name/file.jpg', File::join_paths('name/', '/', 'file.jpg')); - $this->assertEquals('file.jpg', File::join_paths('/', '/', 'file.jpg')); - $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) - { - $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 - */ - protected function getAssetStore() - { - return Injector::inst()->get('AssetStore'); - } -} diff --git a/tests/php/Assets/FileTest.yml b/tests/php/Assets/FileTest.yml deleted file mode 100644 index c6f762a1c..000000000 --- a/tests/php/Assets/FileTest.yml +++ /dev/null @@ -1,84 +0,0 @@ -SilverStripe\Assets\Folder: - subfolder: - Name: FileTest-subfolder - folder1: - Name: FileTest-folder1 - folder2: - Name: FileTest-folder2 - folder1-subfolder1: - Name: FileTest-folder1-subfolder1 - ParentID: =>SilverStripe\Assets\Folder.folder1 -SilverStripe\Assets\File: - asdf: - FileFilename: FileTest.txt - FileHash: 55b443b60176235ef09801153cca4e6da7494a0c - Name: FileTest.txt - pdf: - FileFilename: FileTest.pdf - FileHash: 55b443b60176235ef09801153cca4e6da7494a0c - Name: FileTest.pdf - subfolderfile: - FileFilename: FileTest-subfolder/FileTestSubfolder.txt - FileHash: 55b443b60176235ef09801153cca4e6da7494a0c - Name: FileTestSubfolder.txt - ParentID: =>SilverStripe\Assets\Folder.subfolder - subfolderfile-setfromname: - FileFilename: FileTest-subfolder/FileTestSubfolder2.txt - FileHash: 55b443b60176235ef09801153cca4e6da7494a0c - Name: FileTestSubfolder2.txt - ParentID: =>SilverStripe\Assets\Folder.subfolder - file1-folder1: - FileFilename: FileTest-folder1/File1.txt - FileHash: 55b443b60176235ef09801153cca4e6da7494a0c - Name: File1.txt - ParentID: =>SilverStripe\Assets\Folder.folder1 -SilverStripe\Assets\Image: - gif: - FileFilename: FileTest.gif - FileHash: 55b443b60176235ef09801153cca4e6da7494a0c - Name: FileTest.gif - gifupper: - FileFilename: FileTest-gifupper.GIF - FileHash: 55b443b60176235ef09801153cca4e6da7494a0c - Name: FileTest-gifupper.GIF - setfromname: - FileFilename: FileTest.png - FileHash: 55b443b60176235ef09801153cca4e6da7494a0c - Name: FileTest.png -'SilverStripe\Security\Permission': - admin: - Code: ADMIN - cmsmain: - Code: CMS_ACCESS_LeftAndMain - assetadmin: - Code: CMS_ACCESS_AssetAdmin - securityadmin: - Code: CMS_ACCESS_SecurityAdmin -'SilverStripe\Security\Group': - admins: - Title: Administrators - Permissions: '=>SilverStripe\Security\Permission.admin' - cmsusers: - Title: 'CMS Users' - Permissions: '=>SilverStripe\Security\Permission.cmsmain' - securityusers: - Title: 'Security Users' - Permissions: '=>SilverStripe\Security\Permission.securityadmin' - assetusers: - Title: 'Asset Users' - Permissions: '=>SilverStripe\Security\Permission.assetadmin' -'SilverStripe\Security\Member': - frontend: - Email: frontend@example.com - cms: - Email: cms@silverstripe.com - Groups: '=>SilverStripe\Security\Group.cmsusers' - admin: - Email: admin@silverstripe.com - Groups: '=>SilverStripe\Security\Group.admins' - assetadmin: - Email: assetadmin@silverstripe.com - Groups: '=>SilverStripe\Security\Group.assetusers' - security: - Email: security@silverstripe.com - Groups: '=>SilverStripe\Security\Group.securityusers' diff --git a/tests/php/Assets/FileTest/MyCustomFile.php b/tests/php/Assets/FileTest/MyCustomFile.php deleted file mode 100644 index 27d346dd9..000000000 --- a/tests/php/Assets/FileTest/MyCustomFile.php +++ /dev/null @@ -1,11 +0,0 @@ -rootDir = ASSETS_PATH . '/AssetAdapterTest'; - Filesystem::makeFolder($this->rootDir); - Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', '/'); - $this->originalServer = $_SERVER; - } - - public function tearDown() - { - if ($this->rootDir) { - Filesystem::removeFolder($this->rootDir); - $this->rootDir = null; - } - if ($this->originalServer) { - $_SERVER = $this->originalServer; - $this->originalServer = null; - } - parent::tearDown(); - } - - public function testPublicAdapter() - { - $_SERVER['SERVER_SOFTWARE'] = 'Apache/2.2.22 (Win64) PHP/5.3.13'; - $adapter = new PublicAssetAdapter($this->rootDir); - $this->assertFileExists($this->rootDir . '/.htaccess'); - $this->assertFileNotExists($this->rootDir . '/web.config'); - - $htaccess = $adapter->read('.htaccess'); - $content = $htaccess['contents']; - // Allowed extensions set - $this->assertContains('RewriteCond %{REQUEST_URI} !\\.(?i:', $content); - foreach (File::config()->allowed_extensions as $extension) { - $this->assertRegExp('/\b'.preg_quote($extension).'\b/', $content); - } - - // Rewrite rules - $this->assertContains('RewriteRule .* ../framework/main.php?url=%1 [QSA]', $content); - $this->assertContains('RewriteRule error[^\\\\/]*\\.html$ - [L]', $content); - - // Test flush restores invalid content - \file_put_contents($this->rootDir . '/.htaccess', '# broken content'); - $adapter->flush(); - $htaccess2 = $adapter->read('.htaccess'); - $this->assertEquals($content, $htaccess2['contents']); - - // Test URL - $this->assertEquals('/assets/AssetAdapterTest/file.jpg', $adapter->getPublicUrl('file.jpg')); - } - - public function testProtectedAdapter() - { - $_SERVER['SERVER_SOFTWARE'] = 'Apache/2.2.22 (Win64) PHP/5.3.13'; - $adapter = new ProtectedAssetAdapter($this->rootDir . '/.protected'); - $this->assertFileExists($this->rootDir . '/.protected/.htaccess'); - $this->assertFileNotExists($this->rootDir . '/.protected/web.config'); - - // Test url - $this->assertEquals('/assets/file.jpg', $adapter->getProtectedUrl('file.jpg')); - } -} diff --git a/tests/php/Assets/FolderTest.php b/tests/php/Assets/FolderTest.php deleted file mode 100644 index 493c77984..000000000 --- a/tests/php/Assets/FolderTest.php +++ /dev/null @@ -1,302 +0,0 @@ -logInWithPermission('ADMIN'); - Versioned::set_stage(Versioned::DRAFT); - - // Set backend root to /FolderTest - TestAssetStore::activate('FolderTest'); - - // Set the File Name Filter replacements so files have the expected names - Config::inst()->update( - 'SilverStripe\\Assets\\FileNameFilter', - 'default_replacements', - array( - '/\s/' => '-', // remove whitespace - '/_/' => '-', // underscores to dashes - '/[^A-Za-z0-9+.\-]+/' => '', // remove non-ASCII chars, only allow alphanumeric plus dash and dot - '/[\-]{2,}/' => '-', // remove duplicate dashes - '/^[\.\-_]+/' => '', // Remove all leading dots, dashes or underscores - ) - ); - - // Create a test folders for each of the fixture references - foreach (Folder::get() as $folder) { - $path = TestAssetStore::getLocalPath($folder); - Filesystem::makeFolder($path); - } - - // Create a test files for each of the fixture references - $files = File::get()->exclude('ClassName', Folder::class); - foreach ($files as $file) { - $path = TestAssetStore::getLocalPath($file); - Filesystem::makeFolder(dirname($path)); - $fh = fopen($path, "w+"); - fwrite($fh, str_repeat('x', 1000000)); - fclose($fh); - } - } - - public function tearDown() - { - TestAssetStore::reset(); - parent::tearDown(); - } - - public function testCreateFromNameAndParentIDSetsFilename() - { - $folder1 = $this->objFromFixture(Folder::class, 'folder1'); - $newFolder = new Folder(); - $newFolder->Name = 'CreateFromNameAndParentID'; - $newFolder->ParentID = $folder1->ID; - $newFolder->write(); - - $this->assertEquals($folder1->Filename . 'CreateFromNameAndParentID/', $newFolder->Filename); - } - - public function testRenamesDuplicateFolders() - { - $original = new Folder(); - $original->update([ - 'Name' => 'folder1', - 'ParentID' => 0 - ]); - $original->write(); - - $duplicate = new Folder(); - $duplicate->update([ - 'Name' => 'folder1', - 'ParentID' => 0 - ]); - $duplicate->write(); - - $original = Folder::get()->byID($original->ID); - - $this->assertEquals($original->Name, 'folder1'); - $this->assertEquals($original->Title, 'folder1'); - $this->assertEquals($duplicate->Name, 'folder1-v2'); - $this->assertEquals($duplicate->Title, 'folder1-v2'); - } - - public function testAllChildrenIncludesFolders() - { - $folder1 = $this->objFromFixture(Folder::class, 'folder1'); - $subfolder1 = $this->objFromFixture(Folder::class, 'folder1-subfolder1'); - $file1 = $this->objFromFixture(File::class, 'file1-folder1'); - - $children = $folder1->allChildren(); - $this->assertEquals(2, $children->Count()); - $this->assertContains($subfolder1->ID, $children->column('ID')); - $this->assertContains($file1->ID, $children->column('ID')); - } - - public function testFindOrMake() - { - $path = 'parent/testFindOrMake/'; - $folder = Folder::find_or_make($path); - $this->assertEquals( - ASSETS_PATH . '/FolderTest/' . $path, - TestAssetStore::getLocalPath($folder), - 'Nested path information is correctly saved to database (with trailing slash)' - ); - - // Folder does not exist until it contains files - $this->assertFileNotExists( - TestAssetStore::getLocalPath($folder), - 'Empty folder does not have a filesystem record automatically' - ); - - $parentFolder = DataObject::get_one( - Folder::class, - array( - '"File"."Name"' => 'parent' - ) - ); - $this->assertNotNull($parentFolder); - $this->assertEquals($parentFolder->ID, $folder->ParentID); - - $path = 'parent/testFindOrMake'; // no trailing slash - $folder = Folder::find_or_make($path); - $this->assertEquals( - ASSETS_PATH . '/FolderTest/' . $path . '/', // Slash is automatically added here - TestAssetStore::getLocalPath($folder), - 'Path information is correctly saved to database (without trailing slash)' - ); - - $path = 'assets/'; // relative to "assets/" folder, should produce "assets/assets/" - $folder = Folder::find_or_make($path); - $this->assertEquals( - ASSETS_PATH . '/FolderTest/' . $path, - TestAssetStore::getLocalPath($folder), - 'A folder named "assets/" within "assets/" is allowed' - ); - } - - /** - * Tests for the bug #5994 - Moving folder after executing Folder::findOrMake will not set the Filenames properly - */ - public function testFindOrMakeFolderThenMove() - { - $folder1 = $this->objFromFixture(Folder::class, 'folder1'); - Folder::find_or_make($folder1->Filename); - $folder2 = $this->objFromFixture(Folder::class, 'folder2'); - - // Publish file1 - /** - * @var File $file1 -*/ - $file1 = DataObject::get_by_id(File::class, $this->idFromFixture(File::class, 'file1-folder1'), false); - $file1->publishRecursive(); - - // set ParentID. This should cause updateFilesystem to be called on all children - $folder1->ParentID = $folder2->ID; - $folder1->write(); - - // Check if the file in the folder moved along - /** - * @var File $file1Draft -*/ - $file1Draft = Versioned::get_by_stage(File::class, Versioned::DRAFT)->byID($file1->ID); - $this->assertFileExists(TestAssetStore::getLocalPath($file1Draft)); - - $this->assertEquals( - 'FileTest-folder2/FileTest-folder1/File1.txt', - $file1Draft->Filename, - 'The file DataObject has updated path' - ); - - // File should be located in new folder - $this->assertEquals( - ASSETS_PATH . '/FolderTest/.protected/FileTest-folder2/FileTest-folder1/55b443b601/File1.txt', - TestAssetStore::getLocalPath($file1Draft) - ); - - // Published (live) version remains in the old location - /** - * @var File $file1Live -*/ - $file1Live = Versioned::get_by_stage(File::class, Versioned::LIVE)->byID($file1->ID); - $this->assertEquals( - ASSETS_PATH . '/FolderTest/FileTest-folder1/55b443b601/File1.txt', - TestAssetStore::getLocalPath($file1Live) - ); - - // Publishing the draft to live should move the new file to the public store - $file1Draft->publishRecursive(); - $this->assertEquals( - ASSETS_PATH . '/FolderTest/FileTest-folder2/FileTest-folder1/55b443b601/File1.txt', - TestAssetStore::getLocalPath($file1Draft) - ); - } - - /** - * Tests for the bug #5994 - if you don't execute get_by_id prior to the rename or move, it will fail. - */ - public function testRenameFolderAndCheckTheFile() - { - // ID is prefixed in case Folder is subclassed by project/other module. - $folder1 = DataObject::get_one( - Folder::class, - array( - '"File"."ID"' => $this->idFromFixture(Folder::class, 'folder1') - ) - ); - - $folder1->Name = 'FileTest-folder1-changed'; - $folder1->write(); - - // Check if the file in the folder moved along - $file1 = DataObject::get_by_id(File::class, $this->idFromFixture(File::class, 'file1-folder1'), false); - $this->assertFileExists( - TestAssetStore::getLocalPath($file1) - ); - $this->assertEquals( - $file1->Filename, - 'FileTest-folder1-changed/File1.txt', - 'The file DataObject path uses renamed folder' - ); - - // File should be located in new folder - $this->assertEquals( - ASSETS_PATH . '/FolderTest/.protected/FileTest-folder1-changed/55b443b601/File1.txt', - TestAssetStore::getLocalPath($file1) - ); - } - - /** - * URL and Link are undefined for folder dataobjects - */ - public function testLinkAndRelativeLink() - { - $folder = $this->objFromFixture(Folder::class, 'folder1'); - $this->assertEmpty($folder->getURL()); - $this->assertEmpty($folder->Link()); - } - - public function testIllegalFilenames() - { - - // Test that generating a filename with invalid characters generates a correctly named folder. - $folder = Folder::find_or_make('/FolderTest/EN_US Lang'); - $this->assertEquals('FolderTest/EN-US-Lang/', $folder->getFilename()); - - // Test repeatitions of folder - $folder2 = Folder::find_or_make('/FolderTest/EN_US Lang'); - $this->assertEquals($folder->ID, $folder2->ID); - - $folder3 = Folder::find_or_make('/FolderTest/EN--US_L!ang'); - $this->assertEquals($folder->ID, $folder3->ID); - - $folder4 = Folder::find_or_make('/FolderTest/EN-US-Lang'); - $this->assertEquals($folder->ID, $folder4->ID); - } - - public function testTitleTiedToName() - { - $newFolder = new Folder(); - - $newFolder->Name = 'TestNameCopiedToTitle'; - $this->assertEquals($newFolder->Name, $newFolder->Title); - $this->assertEquals($newFolder->Title, 'TestNameCopiedToTitle'); - - $newFolder->Title = 'TestTitleCopiedToName'; - $this->assertEquals($newFolder->Name, $newFolder->Title); - $this->assertEquals($newFolder->Title, 'TestTitleCopiedToName'); - - $newFolder->Name = 'TestNameWithIllegalCharactersCopiedToTitle '; - $this->assertEquals($newFolder->Name, $newFolder->Title); - $this->assertEquals($newFolder->Title, 'TestNameWithIllegalCharactersCopiedToTitle '); - - $newFolder->Title = 'TestTitleWithIllegalCharactersCopiedToName '; - $this->assertEquals($newFolder->Name, $newFolder->Title); - $this->assertEquals($newFolder->Title, 'TestTitleWithIllegalCharactersCopiedToName '); - } - - public function testRootFolder() - { - $root = Folder::singleton(); - $this->assertEquals('/', $root->getFilename()); - } -} diff --git a/tests/php/Assets/GDTest.php b/tests/php/Assets/GDTest.php deleted file mode 100644 index da93fadf5..000000000 --- a/tests/php/Assets/GDTest.php +++ /dev/null @@ -1,221 +0,0 @@ - 'test_gif.gif', - 'jpg' => 'test_jpg.jpg', - 'png8' => 'test_png8.png', - 'png32' => 'test_png32.png' - ); - - public function setUp() - { - parent::setUp(); - GDBackend::flush(); - } - - public function tearDown() - { - GDBackend::flush(); - parent::tearDown(); - } - - /** - * Loads all images into an associative array of GD objects. - * Optionally applies an operation to each GD - * - * @param callable $callback Action to perform on each GD - * @return array List of GD - */ - protected function applyToEachImage($callback = null) - { - $gds = array(); - foreach (self::$filenames as $type => $file) { - $fullPath = realpath(__DIR__ . '/GDTest/images/' . $file); - $gd = new GDBackend(); - $gd->loadFrom($fullPath); - if ($callback) { - $gd = $callback($gd); - } - $gds[$type] = $gd; - } - return $gds; - } - - /** - * Takes samples from the given GD at 5 pixel increments - * - * @param GDBackend $gd The source image - * @param integer $horizontal Number of samples to take horizontally - * @param integer $vertical Number of samples to take vertically - * @return array List of colours for each sample, each given as an associative - * array with red, blue, green, and alpha components - */ - protected function sampleAreas(GDBackend $gd, $horizontal = 4, $vertical = 4) - { - $samples = array(); - for ($y = 0; $y < $vertical; $y++) { - for ($x = 0; $x < $horizontal; $x++) { - $colour = imagecolorat($gd->getImageResource(), $x * 5, $y * 5); - $samples[] = imagecolorsforindex($gd->getImageResource(), $colour); - } - } - return $samples; - } - - /** - * Asserts that two colour channels are equivalent within a given tolerance range - * - * @param integer $expected - * @param integer $actual - * @param integer $tolerance - */ - protected function assertColourEquals($expected, $actual, $tolerance = 0) - { - $match = - ($expected + $tolerance >= $actual) && - ($expected - $tolerance <= $actual); - $this->assertTrue($match); - } - - /** - * Asserts that all samples given correctly correspond to a greyscale version - * of the test image pattern - * - * @param array $samples List of 16 colour samples representing each of the 8 x 8 squares on the image pattern - * 8 x 8 squares on the image pattern - * @param int $alphaBits Depth of alpha channel in bits - * @param int $tolerance Reasonable tolerance level for colour comparison - */ - protected function assertGreyscale($samples, $alphaBits = 0, $tolerance = 0) - { - - // Check that all colour samples match - foreach ($samples as $sample) { - $matches = - ($sample['red'] === $sample['green']) && - ($sample['blue'] === $sample['green']); - $this->assertTrue($matches, 'Assert colour is greyscale'); - if (!$matches) { - return; - } - } - - // check various sample points - $this->assertColourEquals(76, $samples[0]['red'], $tolerance); - $this->assertColourEquals(149, $samples[2]['red'], $tolerance); - $this->assertColourEquals(0, $samples[8]['red'], $tolerance); - $this->assertColourEquals(127, $samples[9]['red'], $tolerance); - - // check alpha of various points - switch ($alphaBits) { - case 0: - $this->assertColourEquals(0, $samples[2]['alpha'], $tolerance); - $this->assertColourEquals(0, $samples[12]['alpha'], $tolerance); - break; - case 1: - $this->assertColourEquals(0, $samples[2]['alpha'], $tolerance); - $this->assertColourEquals(127, $samples[12]['alpha'], $tolerance); - break; - default: - $this->assertColourEquals(63, $samples[2]['alpha'], $tolerance); - $this->assertColourEquals(127, $samples[12]['alpha'], $tolerance); - break; - } - } - - /** - * Tests that images are correctly transformed to greyscale - */ - function testGreyscale() - { - - // Apply greyscaling to each image - $images = $this->applyToEachImage( - function (GDBackend $gd) { - return $gd->greyscale(); - } - ); - - // Test GIF (256 colour, transparency) - $samplesGIF = $this->sampleAreas($images['gif']); - $this->assertGreyscale($samplesGIF, 1); - - // Test JPG - $samplesJPG = $this->sampleAreas($images['jpg']); - $this->assertGreyscale($samplesJPG, 0, 4); - - // Test PNG 8 (indexed with alpha transparency) - $samplesPNG8 = $this->sampleAreas($images['png8']); - $this->assertGreyscale($samplesPNG8, 8, 4); - - // Test PNG 32 (full alpha transparency) - $samplesPNG32 = $this->sampleAreas($images['png32']); - $this->assertGreyscale($samplesPNG32, 8); - } - - /** - * Tests that GD doesn't attempt to load images when they're deemed unavailable - * - * @return void - */ - public function testImageSkippedWhenUnavailable() - { - $fullPath = realpath(__DIR__ . '/GDTest/images/test_jpg.jpg'); - $gd = new GDTest\ImageUnavailable(); - $gd->loadFrom($fullPath); - - /* Ensure no image resource is created if the image is unavailable */ - $this->assertNull($gd->getImageResource()); - } - - /** - * Tests the integrity of the manipulation cache when an error occurs - */ - public function testCacheIntegrity() - { - $fullPath = realpath(__DIR__ . '/GDTest/images/nonimagedata.jpg'); - - // Load invalid file - $gd = new GDBackend(); - $gd->loadFrom($fullPath); - - // Cache should refer to this file - $cache = Injector::inst()->get(CacheInterface::class . '.GDBackend_Manipulations'); - $key = sha1(implode('|', array($fullPath, filemtime($fullPath)))); - $data = $cache->get($key); - $this->assertEquals('1', $data); - } - - /** - * Test that GD::failedResample() returns true for the current image - * manipulation only if it previously failed - * - * @return void - */ - public function testFailedResample() - { - $fullPath = realpath(__DIR__ . '/GDTest/images/nonimagedata.jpg'); - $fullPath2 = realpath(__DIR__ . '/GDTest/images/test_gif.gif'); - - // Load invalid file - $gd = new GDBackend(); - $gd->loadFrom($fullPath); - - // Cache should refre to this file - $this->assertTrue($gd->failedResample($fullPath, filemtime($fullPath))); - $this->assertFalse($gd->failedResample($fullPath2, filemtime($fullPath2))); - } -} diff --git a/tests/php/Assets/GDTest/ImageUnavailable.php b/tests/php/Assets/GDTest/ImageUnavailable.php deleted file mode 100644 index 87fe2980b..000000000 --- a/tests/php/Assets/GDTest/ImageUnavailable.php +++ /dev/null @@ -1,15 +0,0 @@ -+Z^oq>@-2P6hk%fRgEu}kOQ z1W%^bGtS!W`SNT^dE=uKQxje+)zFkOusKtlc)vpDGV?oeE9UjzceA#;F6Nu5!!pmm bA>>wx6LWCZ=dQD1J0lN#ms+RBz+epk`AIRd diff --git a/tests/php/Assets/GDTest/images/test_jpg.jpg b/tests/php/Assets/GDTest/images/test_jpg.jpg deleted file mode 100644 index b22caa10a53c359bd4a1a911d799f1cf211695d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8081 zcmeGhYj{&dc5c$7X`8kwZ2=J>q!w70Hm@daBc(Lyb7`RV0gAe89ye(q$qo13v;{u} z5z!TqRoKPHil7Mcfe+**vMz#%E~^5HAhIq3<>3Q?MYO1J_srbfGzE5bcfb8*r_J0m z=bSln&Y3y$6h0CTCq=6|0zQPCPILo8NQKlCLUMp8lmi)+0LuuBX^Sak3@-zoJ~0?7 zQn@i0xTpb0LF6E`jJh@sucn44)HNcIfCj>E9c3agw2F4$7eGm|x}6|tAn1kvQh%6< z&@hekt?)8#THDad@C?_=M6`}3i*`aF>}A?HWHJ~{HuyIbX^mD}kS>*Y3Uj1nv4uhPDVyX4$d-i#41@oMS$=+%2L6Q$dOYzB+H`Y zS(NYz7%3CBQ{|wgf+7hN#z~GSS&~ATtV&5uqb?^>NG6wv8bl>ga#@m0k*riHNYk0aXFZ&y@6#{eGqiF+ zv$ty7GrYCm`z=T4f2^JT#s|@(=c@1F75=pb!@5-vK1OOeX)_CzqZ9qc z>=97s=QGv|sQK7?7k_>7sfqe`@31}Pd?zRMx6I0UwCAqlHJd;1A3uH5=NHGV`g-wF zX57lJcF;Gv7p>X+#HQyrPHLI6tL@^><(XYu-uHJ}< z&d)p4b!eBn>%}2+7aiNaw1m^VGuGArjaemYJpvjw;?FPcd+mWSvjp_Vi`=D70quTK zKy}Mj&*+?ScG9`Ot?0TSpswo881p|`e@HkWoJt$j9k%8|11hoCIg@b6Ba}Ii znuz4r=GP2_sX-<$5o!5^rn&f7=hp`YH=mtz%o`f9;JGqV&qinqAlJYT2NptU5jGI! z$3^)_l!qdoiI5}6Hqqf|NP_XVvBvJe-?}hQOEe5)9PkrfXT%{wTbZmzny8Bmy==yxM#pk9WoTfaUD`i+b50Vd1=y$|MeMEI~I7Ibj4 z@j|1YbH)n}Htgswg!%45y*tRoDP#S7Tf9&e3RcDoU}m>yyJw1joO`kd+7}%tc?M>3 z@RC=n^)gZSXy#K~B&miCUxBj+ujI3{-sYAtU!B(=yD_MlgLHGCr%# zB>XQl;tLg)EyaRtG(V{0Oy=Eeu>@o=Vkw4)$2K;wBLnH1_O6J#g*Stc6kfl@MT zz#j<5qC{QQgr$!oIFOOK;bA~sflq@jZoQnHMBi{M45{HKD?dYEG19UK3_E_pMTVxh zi6v#skK~z<;ZTucXe5mJmq5@t)E_pgy&KuyjqJ~BBP+(r5F8t*1%41cuth`_$cq@{ zhNVf18c-|1Jp4EU!IF!ze}qGe9HGff*(1nk| zg&roP?{G!*#zKQ0jV$koxI9y6UhAe|_Ak?&-n>Pp4S36R6D&@HGvc6I1GUpxx@r2@ zX3z8~o>H%_e3W)%hpi(N3DLYu+Yt(eIa^1WjtI8_jL~|Xmayz2s=e9Spmi`T zt+f;y3Ooj@L2ES@8qGz;#l<7Ea0WN%4JN(ORA4N&nc*;Q(8iJuT4TLFTVrK)tSu;& z>0(B;x3?Fzn+qA%uQ!&KmSPzuQvq-kaMQxPtD_*yn#in9Yh23O|UkIl!hAs4tj5OVox{p1Mk*Jg!s2Mz`%j6BeN0HMRMwP=Uq1-xqwiM?x$#z!SN z(#U1<#9KW?p1Q<`q@R?_x|2Mf5^Cay9a)_4QvqRH{HY7xhQz0??PwKTbCA1m&|OTb z6a{|WlELFmLQi$;De@%tcltj*|4u)@rwIjfvY}QvRsL8Uc@9%(#ZN`=O~g}Bir z-uq!+>zw8LO2h{x3f|H1l}^27tbiWsRPIcAWbW`6Uj6dPr&njAM$M6y^gA*OZeL0J z4!tyBWch_jP)kM0;H?hnCoCBI_CYFt_SEL;5sZ8CL*JyI zU-f$Ej_l6{Y^|TN_bo!kFMTLyxX1iy*OEP=@)T%xY|yuB z-^#ds;K>W)W^YMq&n@43!~IjMH{`6G zdrD(d4SjEY>Y`h;p1EbOe|q1oYxbu`dG@n;AFsbXe3r}K@O|-?=`(lT)ZVt~M9GS6 kf8ViXsm1r#N3VNw-M1T$w$6Xs=9{#C#?A)cQ^J9N0-_>z&j0`b diff --git a/tests/php/Assets/GDTest/images/test_png32.png b/tests/php/Assets/GDTest/images/test_png32.png deleted file mode 100644 index 4a81498949e89ea167378c7929de11d6645c65f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VORcG9VSf`DrEP ziAAXljw$&`sS0kHMXBZaMcKs)&cUXITOXSN)p>ZjIEGmCzMW#o#bCg}?E7+mZu~sE z13YD?AM`59us9ktU1aFF{c^3}%XY!O3ywP!51m<~*vFy$Z}Q((g{R-O6t8?`0kQe@ XL%CE$o5fxO&1CR&^>bP0l+XkK&f`lV diff --git a/tests/php/Assets/GDTest/images/test_png8.png b/tests/php/Assets/GDTest/images/test_png8.png deleted file mode 100644 index 0f432f4ac0435bf647a2134e78c5cfa00caf78f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAd3?%E9GuQzs=3*z$5DpHG+YkL80J)+8J|V9E z|NmzI!vFv4>+Amm1%Vs}h6$|Ju|SHwB*-tAp#v!S|NrKQ*-bzp&H|6fVg?4T*C5Q; z)Xf_O6qG4(jVKAuPb(=;EJ|f?Ovz75RdCBJN-fVX$}U!L4mK^^`q&JpP7I{ZxhOTU zBr`uxAtW<5mBG+R-@ruQ(8zYa;ai{zB~KT}5RRG21&oZ20tpM4jdgWz-mnm1@klZ< fFwoSTc!q`H|0}JU2!qT!K-CPMu6{1-oD!Mexclude('ClassName', Folder::class) as $file) { - /** - * @var File $file -*/ - $path = TestAssetStore::getLocalPath($file); - Filesystem::makeFolder(dirname($path)); - $fh = fopen($path, "w+"); - fwrite($fh, str_repeat('x', 1000000)); - fclose($fh); - - // Create variant for each file - $this->getAssetStore()->setFromString( - str_repeat('y', 100), - $file->Filename, - $file->Hash, - 'variant' - ); - } - } - - /** - * @dataProvider getFilenames - */ - public function testIsValidFilename($name, $isValid) - { - $controller = new ProtectedFileController(); - $this->assertEquals( - $isValid, - $controller->isValidFilename($name), - "Assert filename \"$name\" is " . $isValid ? "valid" : "invalid" - ); - } - - public function getFilenames() - { - return array( - // Valid names - array('name.jpg', true), - array('parent/name.jpg', true), - array('parent/name', true), - array('parent\name.jpg', true), - array('parent\name', true), - array('name', true), - - // Invalid names - array('.invalid/name.jpg', false), - array('.invalid\name.jpg', false), - array('.htaccess', false), - array('test/.htaccess.jpg', false), - array('name/.jpg', false), - array('test\.htaccess.jpg', false), - array('name\.jpg', false) - ); - } - - /** - * Test that certain requests are denied - */ - public function testInvalidRequest() - { - $result = $this->get('assets/.protected/file.jpg'); - $this->assertResponseEquals(400, null, $result); - } - - /** - * Test that invalid files generate 404 response - */ - public function testFileNotFound() - { - $result = $this->get('assets/missing.jpg'); - $this->assertResponseEquals(404, null, $result); - } - - /** - * Check public access to assets is available at the appropriate time - */ - public function testAccessControl() - { - $expectedContent = str_repeat('x', 1000000); - $variantContent = str_repeat('y', 100); - - $result = $this->get('assets/55b443b601/FileTest.txt'); - $this->assertResponseEquals(200, $expectedContent, $result); - $result = $this->get('assets/55b443b601/FileTest__variant.txt'); - $this->assertResponseEquals(200, $variantContent, $result); - - // Make this file protected - $this->getAssetStore()->protect( - 'FileTest.txt', - '55b443b60176235ef09801153cca4e6da7494a0c' - ); - - // Should now return explicitly denied errors - $result = $this->get('assets/55b443b601/FileTest.txt'); - $this->assertResponseEquals(403, null, $result); - $result = $this->get('assets/55b443b601/FileTest__variant.txt'); - $this->assertResponseEquals(403, null, $result); - - // Other assets remain available - $result = $this->get('assets/55b443b601/FileTest.pdf'); - $this->assertResponseEquals(200, $expectedContent, $result); - $result = $this->get('assets/55b443b601/FileTest__variant.pdf'); - $this->assertResponseEquals(200, $variantContent, $result); - - // granting access will allow access - $this->getAssetStore()->grant( - 'FileTest.txt', - '55b443b60176235ef09801153cca4e6da7494a0c' - ); - $result = $this->get('assets/55b443b601/FileTest.txt'); - $this->assertResponseEquals(200, $expectedContent, $result); - $result = $this->get('assets/55b443b601/FileTest__variant.txt'); - $this->assertResponseEquals(200, $variantContent, $result); - - // Revoking access will remove access again - $this->getAssetStore()->revoke( - 'FileTest.txt', - '55b443b60176235ef09801153cca4e6da7494a0c' - ); - $result = $this->get('assets/55b443b601/FileTest.txt'); - $this->assertResponseEquals(403, null, $result); - $result = $this->get('assets/55b443b601/FileTest__variant.txt'); - $this->assertResponseEquals(403, null, $result); - - // Moving file back to public store restores access - $this->getAssetStore()->publish( - 'FileTest.txt', - '55b443b60176235ef09801153cca4e6da7494a0c' - ); - $result = $this->get('assets/55b443b601/FileTest.txt'); - $this->assertResponseEquals(200, $expectedContent, $result); - $result = $this->get('assets/55b443b601/FileTest__variant.txt'); - $this->assertResponseEquals(200, $variantContent, $result); - - // Deleting the file will make the response 404 - $this->getAssetStore()->delete( - 'FileTest.txt', - '55b443b60176235ef09801153cca4e6da7494a0c' - ); - $result = $this->get('assets/55b443b601/FileTest.txt'); - $this->assertResponseEquals(404, null, $result); - $result = $this->get('assets/55b443b601/FileTest__variant.txt'); - $this->assertResponseEquals(404, null, $result); - } - - /** - * Test that access to folders is not permitted - */ - public function testFolders() - { - $result = $this->get('assets/55b443b601'); - $this->assertResponseEquals(403, null, $result); - - $result = $this->get('assets/FileTest-subfolder'); - $this->assertResponseEquals(403, null, $result); - - // Flysystem reports root folder as not present - $result = $this->get('assets'); - $this->assertResponseEquals(404, null, $result); - } - - /** - * @return AssetStore - */ - protected function getAssetStore() - { - return Injector::inst()->get('AssetStore'); - } - - /** - * Assert that a response matches the given parameters - * - * @param int $code HTTP code - * @param string $body Body expected for 200 responses - * @param HTTPResponse $response - */ - protected function assertResponseEquals($code, $body, HTTPResponse $response) - { - $this->assertEquals($code, $response->getStatusCode()); - if ($code === 200) { - $this->assertFalse($response->isError()); - $this->assertEquals($body, $response->getBody()); - $this->assertEquals('text/plain', $response->getHeader('Content-Type')); - } else { - $this->assertTrue($response->isError()); - } - } -} diff --git a/tests/php/Assets/Storage/AssetStoreTest.php b/tests/php/Assets/Storage/AssetStoreTest.php deleted file mode 100644 index f67e63d89..000000000 --- a/tests/php/Assets/Storage/AssetStoreTest.php +++ /dev/null @@ -1,523 +0,0 @@ -get('AssetStore'); - } - - /** - * Test different storage methods - */ - public function testStorageMethods() - { - $backend = $this->getBackend(); - - // Test setFromContent - $puppies1 = 'puppies'; - $puppies1Tuple = $backend->setFromString($puppies1, 'pets/my-puppy.txt'); - $this->assertEquals( - array( - 'Hash' => '2a17a9cb4be918774e73ba83bd1c1e7d000fdd53', - 'Filename' => 'pets/my-puppy.txt', - 'Variant' => '', - ), - $puppies1Tuple - ); - - // Test setFromStream (seekable) - $fish1 = realpath(__DIR__ . '/../../ORM/ImageTest/test-image-high-quality.jpg'); - $fish1Stream = fopen($fish1, 'r'); - $fish1Tuple = $backend->setFromStream($fish1Stream, 'parent/awesome-fish.jpg'); - fclose($fish1Stream); - $this->assertEquals( - array( - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'parent/awesome-fish.jpg', - 'Variant' => '', - ), - $fish1Tuple - ); - - // Test with non-seekable streams - TestAssetStore::$seekable_override = false; - $fish2 = realpath(__DIR__ . '/../../ORM/ImageTest/test-image-low-quality.jpg'); - $fish2Stream = fopen($fish2, 'r'); - $fish2Tuple = $backend->setFromStream($fish2Stream, 'parent/mediocre-fish.jpg'); - fclose($fish2Stream); - - $this->assertEquals( - array( - 'Hash' => '33be1b95cba0358fe54e8b13532162d52f97421c', - 'Filename' => 'parent/mediocre-fish.jpg', - 'Variant' => '', - ), - $fish2Tuple - ); - TestAssetStore::$seekable_override = null; - } - - /** - * Test that the backend correctly resolves conflicts - */ - public function testConflictResolution() - { - $backend = $this->getBackend(); - - // Put a file in - $fish1 = realpath(__DIR__ . '/../../ORM/ImageTest/test-image-high-quality.jpg'); - $this->assertFileExists($fish1); - $fish1Tuple = $backend->setFromLocalFile($fish1, 'directory/lovely-fish.jpg'); - $this->assertEquals( - array( - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'directory/lovely-fish.jpg', - 'Variant' => '', - ), - $fish1Tuple - ); - $this->assertEquals( - '/assets/AssetStoreTest/directory/a870de278b/lovely-fish.jpg', - $backend->getAsURL($fish1Tuple['Filename'], $fish1Tuple['Hash']) - ); - - // Write a different file with same name. Should not detect duplicates since sha are different - $fish2 = realpath(__DIR__ . '/../../ORM/ImageTest/test-image-low-quality.jpg'); - try { - $fish2Tuple = $backend->setFromLocalFile( - $fish2, - 'directory/lovely-fish.jpg', - null, - null, - array('conflict' => AssetStore::CONFLICT_EXCEPTION) - ); - } catch (Exception $ex) { - $this->fail('Writing file with different sha to same location failed with exception'); - return; - } - $this->assertEquals( - array( - 'Hash' => '33be1b95cba0358fe54e8b13532162d52f97421c', - 'Filename' => 'directory/lovely-fish.jpg', - 'Variant' => '', - ), - $fish2Tuple - ); - $this->assertEquals( - '/assets/AssetStoreTest/directory/33be1b95cb/lovely-fish.jpg', - $backend->getAsURL($fish2Tuple['Filename'], $fish2Tuple['Hash']) - ); - - // Write original file back with rename - $this->assertFileExists($fish1); - $fish3Tuple = $backend->setFromLocalFile( - $fish1, - 'directory/lovely-fish.jpg', - null, - null, - array('conflict' => AssetStore::CONFLICT_RENAME) - ); - $this->assertEquals( - array( - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'directory/lovely-fish-v2.jpg', - 'Variant' => '', - ), - $fish3Tuple - ); - $this->assertEquals( - '/assets/AssetStoreTest/directory/a870de278b/lovely-fish-v2.jpg', - $backend->getAsURL($fish3Tuple['Filename'], $fish3Tuple['Hash']) - ); - - // Write another file should increment to -v3 - $fish4Tuple = $backend->setFromLocalFile( - $fish1, - 'directory/lovely-fish-v2.jpg', - null, - null, - array('conflict' => AssetStore::CONFLICT_RENAME) - ); - $this->assertEquals( - array( - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'directory/lovely-fish-v3.jpg', - 'Variant' => '', - ), - $fish4Tuple - ); - $this->assertEquals( - '/assets/AssetStoreTest/directory/a870de278b/lovely-fish-v3.jpg', - $backend->getAsURL($fish4Tuple['Filename'], $fish4Tuple['Hash']) - ); - - // Test conflict use existing file - $fish5Tuple = $backend->setFromLocalFile( - $fish1, - 'directory/lovely-fish.jpg', - null, - null, - array('conflict' => AssetStore::CONFLICT_USE_EXISTING) - ); - $this->assertEquals( - array( - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'directory/lovely-fish.jpg', - 'Variant' => '', - ), - $fish5Tuple - ); - $this->assertEquals( - '/assets/AssetStoreTest/directory/a870de278b/lovely-fish.jpg', - $backend->getAsURL($fish5Tuple['Filename'], $fish5Tuple['Hash']) - ); - - // Test conflict use existing file - $fish6Tuple = $backend->setFromLocalFile( - $fish1, - 'directory/lovely-fish.jpg', - null, - null, - array('conflict' => AssetStore::CONFLICT_OVERWRITE) - ); - $this->assertEquals( - array( - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'directory/lovely-fish.jpg', - 'Variant' => '', - ), - $fish6Tuple - ); - $this->assertEquals( - '/assets/AssetStoreTest/directory/a870de278b/lovely-fish.jpg', - $backend->getAsURL($fish6Tuple['Filename'], $fish6Tuple['Hash']) - ); - } - - /** - * Test that flysystem can regenerate the original filename from fileID - */ - public function testGetOriginalFilename() - { - $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') - ); - } - - /** - * Test internal file Id generation - */ - public function testGetFileID() - { - $store = new TestAssetStore(); - $this->assertEquals( - 'directory/2a17a9cb4b/file.jpg', - $store->getFileID('directory/file.jpg', sha1('puppies')) - ); - $this->assertEquals( - '2a17a9cb4b/file.jpg', - $store->getFileID('file.jpg', sha1('puppies')) - ); - $this->assertEquals( - 'dir_ectory/2a17a9cb4b/fil_e.jpg', - $store->getFileID('dir__ectory/fil__e.jpg', sha1('puppies')) - ); - $this->assertEquals( - 'directory/2a17a9cb4b/file_variant.jpg', - $store->getFileID('directory/file__variant.jpg', sha1('puppies'), null) - ); - $this->assertEquals( - 'directory/2a17a9cb4b/file__variant.jpg', - $store->getFileID('directory/file.jpg', sha1('puppies'), 'variant') - ); - $this->assertEquals( - '2a17a9cb4b/file__var__iant.jpg', - $store->getFileID('file.jpg', sha1('puppies'), 'var__iant') - ); - } - - public function testGetMetadata() - { - $backend = $this->getBackend(); - - // jpg - $fish = realpath(__DIR__ . '/../../ORM/ImageTest/test-image-high-quality.jpg'); - $fishTuple = $backend->setFromLocalFile($fish, 'parent/awesome-fish.jpg'); - $this->assertEquals( - 'image/jpeg', - $backend->getMimeType($fishTuple['Filename'], $fishTuple['Hash']) - ); - $fishMeta = $backend->getMetadata($fishTuple['Filename'], $fishTuple['Hash']); - $this->assertEquals(151889, $fishMeta['size']); - $this->assertEquals('file', $fishMeta['type']); - $this->assertNotEmpty($fishMeta['timestamp']); - - // text - $puppies = 'puppies'; - $puppiesTuple = $backend->setFromString($puppies, 'pets/my-puppy.txt'); - $this->assertEquals( - 'text/plain', - $backend->getMimeType($puppiesTuple['Filename'], $puppiesTuple['Hash']) - ); - $puppiesMeta = $backend->getMetadata($puppiesTuple['Filename'], $puppiesTuple['Hash']); - $this->assertEquals(7, $puppiesMeta['size']); - $this->assertEquals('file', $puppiesMeta['type']); - $this->assertNotEmpty($puppiesMeta['timestamp']); - } - - /** - * Test that legacy filenames work as expected - */ - public function testLegacyFilenames() - { - Config::modify()->set(FlysystemAssetStore::class, 'legacy_filenames', true); - - $backend = $this->getBackend(); - - // Put a file in - $fish1 = realpath(__DIR__ . '/../../ORM/ImageTest/test-image-high-quality.jpg'); - $this->assertFileExists($fish1); - $fish1Tuple = $backend->setFromLocalFile($fish1, 'directory/lovely-fish.jpg'); - $this->assertEquals( - array( - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'directory/lovely-fish.jpg', - 'Variant' => '', - ), - $fish1Tuple - ); - $this->assertEquals( - '/assets/AssetStoreTest/directory/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__ . '/../../ORM/ImageTest/test-image-low-quality.jpg'); - try { - $backend->setFromLocalFile( - $fish2, - 'directory/lovely-fish.jpg', - null, - null, - array('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, - array('conflict' => AssetStore::CONFLICT_RENAME) - ); - $this->assertEquals( - array( - 'Hash' => '33be1b95cba0358fe54e8b13532162d52f97421c', - 'Filename' => 'directory/lovely-fish-v2.jpg', - 'Variant' => '', - ), - $fish3Tuple - ); - $this->assertEquals( - '/assets/AssetStoreTest/directory/lovely-fish-v2.jpg', - $backend->getAsURL($fish3Tuple['Filename'], $fish3Tuple['Hash']) - ); - - // Write back original file, but with CONFLICT_EXISTING. The file should not change - $fish4Tuple = $backend->setFromLocalFile( - $fish1, - 'directory/lovely-fish-v2.jpg', - null, - null, - array('conflict' => AssetStore::CONFLICT_USE_EXISTING) - ); - $this->assertEquals( - array( - 'Hash' => '33be1b95cba0358fe54e8b13532162d52f97421c', - 'Filename' => 'directory/lovely-fish-v2.jpg', - 'Variant' => '', - ), - $fish4Tuple - ); - $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, - array('conflict' => AssetStore::CONFLICT_OVERWRITE) - ); - $this->assertEquals( - array( - 'Hash' => 'a870de278b475cb75f5d9f451439b2d378e13af1', - 'Filename' => 'directory/lovely-fish-v2.jpg', - 'Variant' => '', - ), - $fish5Tuple - ); - $this->assertEquals( - '/assets/AssetStoreTest/directory/lovely-fish-v2.jpg', - $backend->getAsURL($fish5Tuple['Filename'], $fish5Tuple['Hash']) - ); - } - - /** - * Test default conflict resolution - */ - public function testDefaultConflictResolution() - { - $store = $this->getBackend(); - - // Disable legacy filenames - Config::modify()->set(FlysystemAssetStore::class, 'legacy_filenames', false); - $this->assertEquals(AssetStore::CONFLICT_OVERWRITE, $store->getDefaultConflictResolution(null)); - $this->assertEquals(AssetStore::CONFLICT_OVERWRITE, $store->getDefaultConflictResolution('somevariant')); - - // Enable legacy filenames - Config::modify()->set(FlysystemAssetStore::class, 'legacy_filenames', true); - $this->assertEquals(AssetStore::CONFLICT_RENAME, $store->getDefaultConflictResolution(null)); - $this->assertEquals(AssetStore::CONFLICT_OVERWRITE, $store->getDefaultConflictResolution('somevariant')); - } - - /** - * Test protect / publish mechanisms - */ - public function testProtect() - { - $backend = $this->getBackend(); - $fish = realpath(__DIR__ . '/../../ORM/ImageTest/test-image-high-quality.jpg'); - $fishTuple = $backend->setFromLocalFile($fish, 'parent/lovely-fish.jpg'); - $fishVariantTuple = $backend->setFromLocalFile($fish, $fishTuple['Filename'], $fishTuple['Hash'], 'copy'); - - // Test public file storage - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/parent/a870de278b/lovely-fish.jpg'); - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/parent/a870de278b/lovely-fish__copy.jpg'); - $this->assertEquals( - AssetStore::VISIBILITY_PUBLIC, - $backend->getVisibility($fishTuple['Filename'], $fishTuple['Hash']) - ); - $this->assertEquals( - '/assets/AssetStoreTest/parent/a870de278b/lovely-fish.jpg', - $backend->getAsURL($fishTuple['Filename'], $fishTuple['Hash']) - ); - $this->assertEquals( - '/assets/AssetStoreTest/parent/a870de278b/lovely-fish__copy.jpg', - $backend->getAsURL($fishVariantTuple['Filename'], $fishVariantTuple['Hash'], $fishVariantTuple['Variant']) - ); - - // Test access rights to public files cannot be revoked - $backend->revoke($fishTuple['Filename'], $fishTuple['Hash']); // can't revoke public assets - $this->assertTrue($backend->canView($fishTuple['Filename'], $fishTuple['Hash'])); - - // Test protected file storage - $backend->protect($fishTuple['Filename'], $fishTuple['Hash']); - $this->assertFileNotExists(ASSETS_PATH . '/AssetStoreTest/parent/a870de278b/lovely-fish.jpg'); - $this->assertFileNotExists(ASSETS_PATH . '/AssetStoreTest/parent/a870de278b/lovely-fish__copy.jpg'); - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/.protected/parent/a870de278b/lovely-fish.jpg'); - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/.protected/parent/a870de278b/lovely-fish__copy.jpg'); - $this->assertEquals( - AssetStore::VISIBILITY_PROTECTED, - $backend->getVisibility($fishTuple['Filename'], $fishTuple['Hash']) - ); - - // Test access rights - $backend->revoke($fishTuple['Filename'], $fishTuple['Hash']); - $this->assertFalse($backend->canView($fishTuple['Filename'], $fishTuple['Hash'])); - $backend->grant($fishTuple['Filename'], $fishTuple['Hash']); - $this->assertTrue($backend->canView($fishTuple['Filename'], $fishTuple['Hash'])); - - // Protected urls should go through asset routing mechanism - $this->assertEquals( - '/assets/parent/a870de278b/lovely-fish.jpg', - $backend->getAsURL($fishTuple['Filename'], $fishTuple['Hash']) - ); - $this->assertEquals( - '/assets/parent/a870de278b/lovely-fish__copy.jpg', - $backend->getAsURL($fishVariantTuple['Filename'], $fishVariantTuple['Hash'], $fishVariantTuple['Variant']) - ); - - // Publish reverts visibility - $backend->publish($fishTuple['Filename'], $fishTuple['Hash']); - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/parent/a870de278b/lovely-fish.jpg'); - $this->assertFileExists(ASSETS_PATH . '/AssetStoreTest/parent/a870de278b/lovely-fish__copy.jpg'); - $this->assertFileNotExists(ASSETS_PATH . '/AssetStoreTest/.protected/parent/a870de278b/lovely-fish.jpg'); - $this->assertFileNotExists(ASSETS_PATH . '/AssetStoreTest/.protected/parent/a870de278b/lovely-fish__copy.jpg'); - $this->assertEquals( - AssetStore::VISIBILITY_PUBLIC, - $backend->getVisibility($fishTuple['Filename'], $fishTuple['Hash']) - ); - } -} diff --git a/tests/php/Assets/Storage/AssetStoreTest/TestAssetStore.php b/tests/php/Assets/Storage/AssetStoreTest/TestAssetStore.php deleted file mode 100644 index 6f90343d9..000000000 --- a/tests/php/Assets/Storage/AssetStoreTest/TestAssetStore.php +++ /dev/null @@ -1,183 +0,0 @@ - AdapterInterface::VISIBILITY_PUBLIC - ] - ); - $protectedAdapter = new ProtectedAssetAdapter(ASSETS_PATH . '/' . $basedir . '/.protected'); - $protectedFilesystem = new Filesystem( - $protectedAdapter, - [ - 'visibility' => AdapterInterface::VISIBILITY_PRIVATE - ] - ); - - $backend = new TestAssetStore(); - $backend->setPublicFilesystem($publicFilesystem); - $backend->setProtectedFilesystem($protectedFilesystem); - Injector::inst()->registerService($backend, 'AssetStore'); - - // Assign flysystem backend to generated asset handler at the same time - $generated = new GeneratedAssetHandler(); - $generated->setFilesystem($publicFilesystem); - Injector::inst()->registerService($generated, 'GeneratedAssetHandler'); - Requirements::backend()->setAssetHandler($generated); - - // Disable legacy and set defaults - FlysystemAssetStore::config()->set('legacy_filenames', false); - Director::config()->set('alternate_base_url', '/'); - DBFile::config()->set('force_resample', false); - File::config()->set('force_resample', false); - self::reset(); - self::$basedir = $basedir; - - // Ensure basedir exists - SSFilesystem::makeFolder(self::base_path()); - } - - /** - * Get absolute path to basedir - * - * @return string - */ - public static function base_path() - { - if (!self::$basedir) { - return null; - } - return ASSETS_PATH . '/' . self::$basedir; - } - - /** - * Reset defaults for this store - */ - public static function reset() - { - // Remove all files in this store - if (self::$basedir) { - $path = self::base_path(); - if (file_exists($path)) { - SSFilesystem::removeFolder($path); - } - } - self::$seekable_override = null; - self::$basedir = null; - } - - /** - * Helper method to get local filesystem path for this file - * - * @param AssetContainer $asset - * @return string - */ - public static function getLocalPath(AssetContainer $asset) - { - if ($asset instanceof Folder) { - return self::base_path() . '/' . $asset->getFilename(); - } - if ($asset instanceof File) { - $asset = $asset->File; - } - // Extract filesystem used to store this object - /** @var TestAssetStore $assetStore */ - $assetStore = Injector::inst()->get('AssetStore'); - $fileID = $assetStore->getFileID($asset->Filename, $asset->Hash, $asset->Variant); - $filesystem = $assetStore->getProtectedFilesystem(); - if (!$filesystem->has($fileID)) { - $filesystem = $assetStore->getPublicFilesystem(); - } - /** @var Local $adapter */ - $adapter = $filesystem->getAdapter(); - return $adapter->applyPathPrefix($fileID); - } - - public function cleanFilename($filename) - { - return parent::cleanFilename($filename); - } - - public function getFileID($filename, $hash, $variant = null) - { - return parent::getFileID($filename, $hash, $variant); - } - - - public function getOriginalFilename($fileID) - { - return parent::getOriginalFilename($fileID); - } - - public function removeVariant($fileID) - { - return parent::removeVariant($fileID); - } - - public function getDefaultConflictResolution($variant) - { - return parent::getDefaultConflictResolution($variant); - } - - protected function isSeekableStream($stream) - { - if (isset(self::$seekable_override)) { - return self::$seekable_override; - } - return parent::isSeekableStream($stream); - } -} diff --git a/tests/php/Assets/Storage/DefaultAssetNameGeneratorTest.php b/tests/php/Assets/Storage/DefaultAssetNameGeneratorTest.php deleted file mode 100644 index 2427064ed..000000000 --- a/tests/php/Assets/Storage/DefaultAssetNameGeneratorTest.php +++ /dev/null @@ -1,135 +0,0 @@ -update(DefaultAssetNameGenerator::class, 'version_prefix', ''); - $generator = new DefaultAssetNameGenerator('folder/MyFile-001.jpg'); - $suggestions = iterator_to_array($generator); - - // Expect 100 suggestions - $this->assertEquals(100, count($suggestions)); - - // First item is always the same as input - $this->assertEquals('folder/MyFile-001.jpg', $suggestions[0]); - - // Check that padding is respected - $this->assertEquals('folder/MyFile-002.jpg', $suggestions[1]); - $this->assertEquals('folder/MyFile-003.jpg', $suggestions[2]); - $this->assertEquals('folder/MyFile-004.jpg', $suggestions[3]); - $this->assertEquals('folder/MyFile-021.jpg', $suggestions[20]); - $this->assertEquals('folder/MyFile-099.jpg', $suggestions[98]); - - // Last item should be some semi-random string, not in the same numeric sequence - $this->assertNotEquals('folder/MyFile-0100.jpg', $suggestions[99]); - $this->assertNotEquals('folder/MyFile-100.jpg', $suggestions[99]); - - // Test with a value starting above 1 - $generator = new DefaultAssetNameGenerator('folder/MyFile-024.jpg'); - $suggestions = iterator_to_array($generator); - $this->assertEquals(100, count($suggestions)); - $this->assertEquals('folder/MyFile-024.jpg', $suggestions[0]); - $this->assertEquals('folder/MyFile-025.jpg', $suggestions[1]); - $this->assertEquals('folder/MyFile-026.jpg', $suggestions[2]); - $this->assertEquals('folder/MyFile-048.jpg', $suggestions[24]); - $this->assertEquals('folder/MyFile-122.jpg', $suggestions[98]); - $this->assertNotEquals('folder/MyFile-0123.jpg', $suggestions[99]); - $this->assertNotEquals('folder/MyFile-123.jpg', $suggestions[99]); // Last suggestion is semi-random - - // Test without numeric value - $generator = new DefaultAssetNameGenerator('folder/MyFile.jpg'); - $suggestions = iterator_to_array($generator); - $this->assertEquals(100, count($suggestions)); - $this->assertEquals('folder/MyFile.jpg', $suggestions[0]); - $this->assertEquals('folder/MyFile2.jpg', $suggestions[1]); - $this->assertEquals('folder/MyFile3.jpg', $suggestions[2]); - $this->assertEquals('folder/MyFile25.jpg', $suggestions[24]); - $this->assertEquals('folder/MyFile99.jpg', $suggestions[98]); - $this->assertNotEquals('folder/MyFile100.jpg', $suggestions[99]); // Last suggestion is semi-random - } - - /** - * Test with default -v prefix - */ - public function testWithDefaultPrefix() - { - Config::inst()->update(DefaultAssetNameGenerator::class, 'version_prefix', '-v'); - - // Test with item that doesn't contain the prefix - $generator = new DefaultAssetNameGenerator('folder/MyFile-001.jpg'); - $suggestions = iterator_to_array($generator); - $this->assertEquals(100, count($suggestions)); - $this->assertEquals('folder/MyFile-001.jpg', $suggestions[0]); - $this->assertEquals('folder/MyFile-001-v2.jpg', $suggestions[1]); - $this->assertEquals('folder/MyFile-001-v4.jpg', $suggestions[3]); - $this->assertEquals('folder/MyFile-001-v21.jpg', $suggestions[20]); - $this->assertEquals('folder/MyFile-001-v99.jpg', $suggestions[98]); - $this->assertNotEquals('folder/MyFile-001-v100.jpg', $suggestions[99]); // Last suggestion is semi-random - - - // Test with item that contains prefix - $generator = new DefaultAssetNameGenerator('folder/MyFile-v24.jpg'); - $suggestions = iterator_to_array($generator); - $this->assertEquals(100, count($suggestions)); - $this->assertEquals('folder/MyFile-v24.jpg', $suggestions[0]); - $this->assertEquals('folder/MyFile-v25.jpg', $suggestions[1]); - $this->assertEquals('folder/MyFile-v26.jpg', $suggestions[2]); - $this->assertEquals('folder/MyFile-v48.jpg', $suggestions[24]); - $this->assertEquals('folder/MyFile-v122.jpg', $suggestions[98]); - $this->assertNotEquals('folder/MyFile-v123.jpg', $suggestions[99]); - $this->assertNotEquals('folder/MyFile-123.jpg', $suggestions[99]); - - // Test without numeric value - $generator = new DefaultAssetNameGenerator('folder/MyFile.jpg'); - $suggestions = iterator_to_array($generator); - $this->assertEquals(100, count($suggestions)); - $this->assertEquals('folder/MyFile.jpg', $suggestions[0]); - $this->assertEquals('folder/MyFile-v2.jpg', $suggestions[1]); - $this->assertEquals('folder/MyFile-v3.jpg', $suggestions[2]); - $this->assertEquals('folder/MyFile-v25.jpg', $suggestions[24]); - $this->assertEquals('folder/MyFile-v99.jpg', $suggestions[98]); - $this->assertNotEquals('folder/MyFile-v100.jpg', $suggestions[99]); - } - - public function testFolderWithoutDefaultPrefix() - { - Config::inst()->update(DefaultAssetNameGenerator::class, 'version_prefix', ''); - $generator = new DefaultAssetNameGenerator('folder/subfolder'); - $suggestions = iterator_to_array($generator); - - // Expect 100 suggestions - $this->assertEquals(100, count($suggestions)); - - // First item is always the same as input - $this->assertEquals('folder/subfolder', $suggestions[0]); - $this->assertEquals('folder/subfolder2', $suggestions[1]); - } - - public function testFolderWithDefaultPrefix() - { - Config::inst()->update(DefaultAssetNameGenerator::class, 'version_prefix', '-v'); - $generator = new DefaultAssetNameGenerator('folder/subfolder'); - $suggestions = iterator_to_array($generator); - - // Expect 100 suggestions - $this->assertEquals(100, count($suggestions)); - - // First item is always the same as input - $this->assertEquals('folder/subfolder', $suggestions[0]); - $this->assertEquals('folder/subfolder-v2', $suggestions[1]); - } -} diff --git a/tests/php/Assets/UploadTest.php b/tests/php/Assets/UploadTest.php deleted file mode 100644 index bdad0e8dd..000000000 --- a/tests/php/Assets/UploadTest.php +++ /dev/null @@ -1,850 +0,0 @@ -tmpFilePath)) { - unlink($this->tmpFilePath); - } - } - - public function testUpload() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload.txt'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'txt', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - - // test upload into default folder - $u1 = new Upload(); - $u1->setValidator($v); - $u1->loadIntoFile($tmpFile); - $file1 = $u1->getFile(); - $this->assertEquals( - 'Uploads/UploadTest-testUpload.txt', - $file1->getFilename() - ); - $this->assertEquals( - BASE_PATH . '/assets/UploadTest/.protected/Uploads/315ae4c3d4/UploadTest-testUpload.txt', - TestAssetStore::getLocalPath($file1) - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file1), - 'File upload to standard directory in /assets' - ); - - // test upload into custom folder - $customFolder = 'UploadTest-testUpload'; - $u2 = new Upload(); - $u2->loadIntoFile($tmpFile, null, $customFolder); - $file2 = $u2->getFile(); - $this->assertEquals( - 'UploadTest-testUpload/UploadTest-testUpload.txt', - $file2->getFilename() - ); - $this->assertEquals( - BASE_PATH . '/assets/UploadTest/.protected/UploadTest-testUpload/315ae4c3d4/UploadTest-testUpload.txt', - TestAssetStore::getLocalPath($file2) - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file2), - 'File upload to custom directory in /assets' - ); - } - - public function testAllowedFilesize() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload.txt'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'txt', - 'error' => UPLOAD_ERR_OK, - ); - - // test upload into default folder - $u1 = new Upload(); - $v = new UploadTest\Validator(); - - $v->setAllowedMaxFileSize(array('txt' => 10)); - $u1->setValidator($v); - $result = $u1->loadIntoFile($tmpFile); - $this->assertFalse($result, 'Load failed because size was too big'); - - $v->setAllowedMaxFileSize(array('[document]' => 10)); - $u1->setValidator($v); - $result = $u1->loadIntoFile($tmpFile); - $this->assertFalse($result, 'Load failed because size was too big'); - - $v->setAllowedMaxFileSize(array('txt' => 200000)); - $u1->setValidator($v); - $result = $u1->loadIntoFile($tmpFile); - $this->assertTrue($result, 'Load failed with setting max file size'); - - // check max file size set by app category - $tmpFileName = 'UploadTest-testUpload.jpg'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - file_put_contents($this->tmpFilePath, $tmpFileContent . $tmpFileContent); - - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'image/jpeg', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'jpg', - 'error' => UPLOAD_ERR_OK, - ); - - $v->setAllowedMaxFileSize(array('[image]' => '40k')); - $u1->setValidator($v); - $result = $u1->loadIntoFile($tmpFile); - $this->assertTrue($result, 'Load failed with setting max file size'); - - $v->setAllowedMaxFileSize(array('[image]' => '1k')); - $u1->setValidator($v); - $result = $u1->loadIntoFile($tmpFile); - $this->assertFalse($result, 'Load failed because size was too big'); - - $v->setAllowedMaxFileSize(array('[image]' => 1000)); - $u1->setValidator($v); - $result = $u1->loadIntoFile($tmpFile); - $this->assertFalse($result, 'Load failed because size was too big'); - } - - public function testPHPUploadErrors() - { - $configMaxFileSizes = ['*' => '1k']; - Config::inst()->update( - 'SilverStripe\\Assets\\Upload_Validator', - 'default_max_file_size', - $configMaxFileSizes - ); - // create tmp file - $tmpFileName = 'myfile.jpg'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(100); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // Build file - $upload = new Upload(); - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => '', - 'tmp_name' => $this->tmpFilePath, - 'size' => filesize($this->tmpFilePath), - 'error' => UPLOAD_ERR_OK, - ); - - // Test ok - $this->assertTrue($upload->validate($tmpFile)); - - // Test zero size file - $upload->clearErrors(); - $tmpFile['size'] = 0; - $this->assertFalse($upload->validate($tmpFile)); - $this->assertContains( - _t('File.NOFILESIZE', 'Filesize is zero bytes.'), - $upload->getErrors() - ); - - // Test file too large - $upload->clearErrors(); - $tmpFile['error'] = UPLOAD_ERR_INI_SIZE; - $this->assertFalse($upload->validate($tmpFile)); - $this->assertContains( - _t( - 'File.TOOLARGE', - 'Filesize is too large, maximum {size} allowed', - 'Argument 1: Filesize (e.g. 1MB)', - array('size' => '1 KB') - ), - $upload->getErrors() - ); - - // Test form size - $upload->clearErrors(); - $tmpFile['error'] = UPLOAD_ERR_FORM_SIZE; - $this->assertFalse($upload->validate($tmpFile)); - $this->assertContains( - _t( - 'File.TOOLARGE', - 'Filesize is too large, maximum {size} allowed', - 'Argument 1: Filesize (e.g. 1MB)', - array('size' => '1 KB') - ), - $upload->getErrors() - ); - - // Test no file - $upload->clearErrors(); - $tmpFile['error'] = UPLOAD_ERR_NO_FILE; - $this->assertFalse($upload->validate($tmpFile)); - $this->assertContains( - _t('File.NOVALIDUPLOAD', 'File is not a valid upload'), - $upload->getErrors() - ); - } - - public function testGetAllowedMaxFileSize() - { - Config::nest(); - - // Check the max file size uses the config values - $configMaxFileSizes = array( - '[image]' => '1k', - 'txt' => 1000 - ); - Config::inst()->update('SilverStripe\\Assets\\Upload_Validator', 'default_max_file_size', $configMaxFileSizes); - $v = new UploadTest\Validator(); - - $retrievedSize = $v->getAllowedMaxFileSize('[image]'); - $this->assertEquals( - 1024, - $retrievedSize, - 'Max file size check on default values failed (config category set check)' - ); - - $retrievedSize = $v->getAllowedMaxFileSize('txt'); - $this->assertEquals( - 1000, - $retrievedSize, - 'Max file size check on default values failed (config extension set check)' - ); - - // Check instance values for max file size - $maxFileSizes = array( - '[document]' => 2000, - 'txt' => '4k' - ); - $v = new UploadTest\Validator(); - $v->setAllowedMaxFileSize($maxFileSizes); - - $retrievedSize = $v->getAllowedMaxFileSize('[document]'); - $this->assertEquals( - 2000, - $retrievedSize, - 'Max file size check on instance values failed (instance category set check)' - ); - - // Check that the instance values overwrote the default values - // ie. The max file size will not exist for [image] - $retrievedSize = $v->getAllowedMaxFileSize('[image]'); - $this->assertFalse($retrievedSize, 'Max file size check on instance values failed (config overridden check)'); - - // Check a category that has not been set before - $retrievedSize = $v->getAllowedMaxFileSize('[archive]'); - $this->assertFalse($retrievedSize, 'Max file size check on instance values failed (category not set check)'); - - // Check a file extension that has not been set before - $retrievedSize = $v->getAllowedMaxFileSize('mp3'); - $this->assertFalse($retrievedSize, 'Max file size check on instance values failed (extension not set check)'); - - $retrievedSize = $v->getAllowedMaxFileSize('txt'); - $this->assertEquals( - 4096, - $retrievedSize, - 'Max file size check on instance values failed (instance extension set check)' - ); - - // Check a wildcard max file size against a file with an extension - $v = new UploadTest\Validator(); - $v->setAllowedMaxFileSize(2000); - - $retrievedSize = $v->getAllowedMaxFileSize('.jpg'); - $this->assertEquals( - 2000, - $retrievedSize, - 'Max file size check on instance values failed (wildcard max file size)' - ); - - Config::unnest(); - } - - public function testAllowedSizeOnFileWithNoExtension() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => '', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - $v->setAllowedMaxFileSize(array('' => 10)); - - // test upload into default folder - $u1 = new Upload(); - $u1->setValidator($v); - $result = $u1->loadIntoFile($tmpFile); - - $this->assertFalse($result, 'Load failed because size was too big'); - } - - public function testUploadDoesNotAllowUnknownExtension() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload.php'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'php', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - $v->setAllowedExtensions(array('txt')); - - // test upload into default folder - $u = new Upload(); - $u->setValidator($v); - $result = $u->loadIntoFile($tmpFile); - - $this->assertFalse($result, 'Load failed because extension was not accepted'); - } - - public function testUploadAcceptsAllowedExtension() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload.txt'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'txt', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - $v->setAllowedExtensions(array('txt')); - - // test upload into default folder - $u = new Upload(); - $u->setValidator($v); - $u->loadIntoFile($tmpFile); - $file = $u->getFile(); - $this->assertFileExists( - TestAssetStore::getLocalPath($file), - 'File upload to custom directory in /assets' - ); - } - - public function testUploadDeniesNoExtensionFilesIfNoEmptyStringSetForValidatorExtensions() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => '', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - $v->setAllowedExtensions(array('txt')); - - // test upload into default folder - $u = new Upload(); - $result = $u->loadIntoFile($tmpFile); - - $this->assertFalse($result, 'Load failed because extension was not accepted'); - $this->assertEquals(1, count($u->getErrors()), 'There is a single error of the file extension'); - } - - public function testUploadTarGzFileTwiceAppendsNumber() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload.tar.gz'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'tar.gz', - 'error' => UPLOAD_ERR_OK, - ); - - // test upload into default folder - $u = new Upload(); - $u->loadIntoFile($tmpFile); - $file = $u->getFile(); - $this->assertEquals( - 'UploadTest-testUpload.tar.gz', - $file->Name, - 'File has a name without a number because it\'s not a duplicate' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file), - 'File exists' - ); - - $u = new Upload(); - $u->loadIntoFile($tmpFile); - $file2 = $u->getFile(); - $this->assertEquals( - 'UploadTest-testUpload-v2.tar.gz', - $file2->Name, - 'File receives a number attached to the end before the extension' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file2), - 'File exists' - ); - $this->assertGreaterThan( - $file->ID, - $file2->ID, - 'File database record is not the same' - ); - - $u = new Upload(); - $u->loadIntoFile($tmpFile); - $file3 = $u->getFile(); - $this->assertEquals( - 'UploadTest-testUpload-v3.tar.gz', - $file3->Name, - 'File receives a number attached to the end before the extension' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file3), - 'File exists' - ); - $this->assertGreaterThan( - $file2->ID, - $file3->ID, - 'File database record is not the same' - ); - } - - public function testUploadFileWithNoExtensionTwiceAppendsNumber() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'txt', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - $v->setAllowedExtensions(array('')); - - // test upload into default folder - $u = new Upload(); - $u->setValidator($v); - $u->loadIntoFile($tmpFile); - $file = $u->getFile(); - - $this->assertEquals( - 'UploadTest-testUpload', - $file->Name, - 'File is uploaded without extension' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file), - 'File exists' - ); - - $u = new Upload(); - $u->setValidator($v); - $u->loadIntoFile($tmpFile); - $file2 = $u->getFile(); - $this->assertEquals( - 'UploadTest-testUpload-v2', - $file2->Name, - 'File receives a number attached to the end' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file2), - 'File exists' - ); - $this->assertGreaterThan( - $file->ID, - $file2->ID, - 'File database record is not the same' - ); - } - - public function testReplaceFile() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'txt', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - $v->setAllowedExtensions(array('')); - - // test upload into default folder - $u = new Upload(); - $u->setValidator($v); - $u->loadIntoFile($tmpFile); - $file = $u->getFile(); - - $this->assertEquals( - 'UploadTest-testUpload', - $file->Name, - 'File is uploaded without extension' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file), - 'File exists' - ); - - $u = new Upload(); - $u->setValidator($v); - $u->setReplaceFile(true); - $u->loadIntoFile($tmpFile); - $file2 = $u->getFile(); - $this->assertEquals( - 'UploadTest-testUpload', - $file2->Name, - 'File does not receive new name' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file2), - 'File exists' - ); - $this->assertEquals( - $file->ID, - $file2->ID, - 'File database record is the same' - ); - } - - public function testReplaceFileWithLoadIntoFile() - { - // create tmp file - $tmpFileName = 'UploadTest-testUpload.txt'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'txt', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - - // test upload into default folder - $u = new Upload(); - $u->setValidator($v); - $u->loadIntoFile($tmpFile); - $file = $u->getFile(); - - $this->assertEquals( - 'UploadTest-testUpload.txt', - $file->Name, - 'File is uploaded without extension' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file), - 'File exists' - ); - - // replace=true - $u = new Upload(); - $u->setValidator($v); - $u->setReplaceFile(true); - $u->loadIntoFile($tmpFile, new File()); - $file2 = $u->getFile(); - $this->assertEquals( - 'UploadTest-testUpload.txt', - $file2->Name, - 'File does not receive new name' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file2), - 'File exists' - ); - $this->assertEquals( - $file->ID, - $file2->ID, - 'File database record is the same' - ); - - // replace=false - $u = new Upload(); - $u->setValidator($v); - $u->setReplaceFile(false); - $u->loadIntoFile($tmpFile, new File()); - $file3 = $u->getFile(); - $this->assertEquals( - 'UploadTest-testUpload-v2.txt', - $file3->Name, - 'File does receive new name' - ); - $this->assertFileExists( - TestAssetStore::getLocalPath($file3), - 'File exists' - ); - $this->assertGreaterThan( - $file2->ID, - $file3->ID, - 'File database record is not the same' - ); - } - - public function testDeleteResampledImagesOnUpload() - { - $tmpFileName = 'UploadTest-testUpload.jpg'; - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - - $uploadImage = function () use ($tmpFileName) { - copy(__DIR__ . '/GDTest/images/test_jpg.jpg', $this->tmpFilePath); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'jpg', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - - // test upload into default folder - $u = new Upload(); - $u->setReplaceFile(true); - $u->setValidator($v); - $u->loadIntoFile($tmpFile); - return $u->getFile(); - }; - - // Image upload and generate a resampled image - $image = $uploadImage(); - $resampled = $image->ResizedImage(123, 456); - $resampledPath = TestAssetStore::getLocalPath($resampled); - $this->assertFileExists($resampledPath); - - // Re-upload the image, overwriting the original - // Resampled images should removed when their parent file is overwritten - $image = $uploadImage(); - $this->assertFileExists($resampledPath); - } - - public function testFileVersioningWithAnExistingFile() - { - $upload = function ($tmpFileName) { - // create tmp file - $this->tmpFilePath = TEMP_FOLDER . '/' . $tmpFileName; - $tmpFileContent = $this->getTemporaryFileContent(); - file_put_contents($this->tmpFilePath, $tmpFileContent); - - // emulates the $_FILES array - $tmpFile = array( - 'name' => $tmpFileName, - 'type' => 'text/plaintext', - 'size' => filesize($this->tmpFilePath), - 'tmp_name' => $this->tmpFilePath, - 'extension' => 'jpg', - 'error' => UPLOAD_ERR_OK, - ); - - $v = new UploadTest\Validator(); - - // test upload into default folder - $u = new Upload(); - $u->setReplaceFile(false); - $u->setValidator($v); - $u->loadIntoFile($tmpFile); - return $u->getFile(); - }; - - // test empty file version prefix - Config::inst()->update('SilverStripe\\Assets\\Storage\\DefaultAssetNameGenerator', 'version_prefix', ''); - - $file1 = $upload('UploadTest-IMG001.jpg'); - $this->assertEquals( - 'UploadTest-IMG001.jpg', - $file1->Name, - 'File does not receive new name' - ); - - $file2 = $upload('UploadTest-IMG001.jpg'); - $this->assertEquals( - 'UploadTest-IMG002.jpg', - $file2->Name, - 'File does receive new name' - ); - - $file3 = $upload('UploadTest-IMG002.jpg'); - $this->assertEquals( - 'UploadTest-IMG003.jpg', - $file3->Name, - 'File does receive new name' - ); - - $file4 = $upload('UploadTest-IMG3.jpg'); - $this->assertEquals( - 'UploadTest-IMG3.jpg', - $file4->Name, - 'File does not receive new name' - ); - - $file1->delete(); - $file2->delete(); - $file3->delete(); - $file4->delete(); - - // test '-v' file version prefix - Config::inst()->update('SilverStripe\\Assets\\Storage\\DefaultAssetNameGenerator', 'version_prefix', '-v'); - - $file1 = $upload('UploadTest2-IMG001.jpg'); - $this->assertEquals( - 'UploadTest2-IMG001.jpg', - $file1->Name, - 'File does not receive new name' - ); - - $file2 = $upload('UploadTest2-IMG001.jpg'); - $this->assertEquals( - 'UploadTest2-IMG001-v2.jpg', - $file2->Name, - 'File does receive new name' - ); - - $file3 = $upload('UploadTest2-IMG001.jpg'); - $this->assertEquals( - 'UploadTest2-IMG001-v3.jpg', - $file3->Name, - 'File does receive new name' - ); - - $file4 = $upload('UploadTest2-IMG001-v3.jpg'); - $this->assertEquals( - 'UploadTest2-IMG001-v4.jpg', - $file4->Name, - 'File does receive new name' - ); - } - - /** - * Generate some dummy file content - * - * @param int $reps How many zeros to return - * @return string - */ - protected function getTemporaryFileContent($reps = 10000) - { - return str_repeat('0', $reps); - } -} diff --git a/tests/php/Assets/UploadTest/Validator.php b/tests/php/Assets/UploadTest/Validator.php deleted file mode 100644 index 13b878533..000000000 --- a/tests/php/Assets/UploadTest/Validator.php +++ /dev/null @@ -1,44 +0,0 @@ -tmpFile['name']); - // filesize validation - - if (!$this->isValidSize()) { - $ext = (isset($pathInfo['extension'])) ? $pathInfo['extension'] : ''; - $arg = File::format_size($this->getAllowedMaxFileSize($ext)); - $this->errors[] = _t( - 'File.TOOLARGE', - 'File size is too large, maximum {size} allowed', - 'Argument 1: File size (e.g. 1MB)', - array('size' => $arg) - ); - return false; - } - - // extension validation - if (!$this->isValidExtension()) { - $this->errors[] = _t('File.INVALIDEXTENSIONSHORT', 'Extension is not allowed'); - return false; - } - - return true; - } -} diff --git a/tests/php/Forms/DBFileTest.php b/tests/php/Forms/DBFileTest.php deleted file mode 100644 index 0047104b2..000000000 --- a/tests/php/Forms/DBFileTest.php +++ /dev/null @@ -1,101 +0,0 @@ -update('alternate_base_url', '/mysite/'); - } - - public function tearDown() - { - TestAssetStore::reset(); - parent::tearDown(); - } - - /** - * Test that images in a DBFile are rendered properly - */ - public function testRender() - { - $obj = new DBFileTest\TestObject(); - - // Test image tag - $fish = realpath(__DIR__ .'/../ORM/ImageTest/test-image-high-quality.jpg'); - $this->assertFileExists($fish); - $obj->MyFile->setFromLocalFile($fish, 'awesome-fish.jpg'); - $this->assertEquals( - 'awesome-fish.jpg', - trim($obj->MyFile->forTemplate()) - ); - - // Test download tag - $obj->MyFile->setFromString('puppies', 'subdir/puppy-document.txt'); - $this->assertEquals( - '', - trim($obj->MyFile->forTemplate()) - ); - } - - public function testValidation() - { - $obj = new DBFileTest\ImageOnly(); - - // Test from image - $fish = realpath(__DIR__ .'/../ORM/ImageTest/test-image-high-quality.jpg'); - $this->assertFileExists($fish); - $obj->MyFile->setFromLocalFile($fish, 'awesome-fish.jpg'); - - // This should fail - $this->setExpectedException('SilverStripe\\ORM\\ValidationException'); - $obj->MyFile->setFromString('puppies', 'subdir/puppy-document.txt'); - } - - public function testPermission() - { - $obj = new DBFileTest\TestObject(); - - // Test from image - $fish = realpath(__DIR__ .'/../ORM/ImageTest/test-image-high-quality.jpg'); - $this->assertFileExists($fish); - $obj->MyFile->setFromLocalFile( - $fish, - 'private/awesome-fish.jpg', - null, - null, - array( - 'visibility' => AssetStore::VISIBILITY_PROTECTED - ) - ); - - // Test various file permissions work on DBFile - $this->assertFalse($obj->MyFile->canViewFile()); - $obj->MyFile->getURL(); - $this->assertTrue($obj->MyFile->canViewFile()); - $obj->MyFile->revokeFile(); - $this->assertFalse($obj->MyFile->canViewFile()); - $obj->MyFile->getURL(false); - $this->assertFalse($obj->MyFile->canViewFile()); - $obj->MyFile->grantFile(); - $this->assertTrue($obj->MyFile->canViewFile()); - } -} diff --git a/tests/php/Forms/DBFileTest/ImageOnly.php b/tests/php/Forms/DBFileTest/ImageOnly.php deleted file mode 100644 index 708ce3c5f..000000000 --- a/tests/php/Forms/DBFileTest/ImageOnly.php +++ /dev/null @@ -1,19 +0,0 @@ - "DBFile('image/supported')" - ); -} diff --git a/tests/php/Forms/DBFileTest/Subclass.php b/tests/php/Forms/DBFileTest/Subclass.php deleted file mode 100644 index 26990ff10..000000000 --- a/tests/php/Forms/DBFileTest/Subclass.php +++ /dev/null @@ -1,18 +0,0 @@ - "DBFile" - ); -} diff --git a/tests/php/Forms/DBFileTest/TestObject.php b/tests/php/Forms/DBFileTest/TestObject.php deleted file mode 100644 index 6ed1f3883..000000000 --- a/tests/php/Forms/DBFileTest/TestObject.php +++ /dev/null @@ -1,19 +0,0 @@ - "DBFile" - ); -} diff --git a/tests/php/ORM/DataDifferencerTest.php b/tests/php/ORM/DataDifferencerTest.php index 1d4d072ae..3601317c0 100644 --- a/tests/php/ORM/DataDifferencerTest.php +++ b/tests/php/ORM/DataDifferencerTest.php @@ -13,7 +13,6 @@ use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore; class DataDifferencerTest extends SapphireTest { - protected static $fixture_file = 'DataDifferencerTest.yml'; protected $extraDataObjects = array( @@ -33,7 +32,7 @@ class DataDifferencerTest extends SapphireTest // Create a test files for each of the fixture references $files = File::get()->exclude('ClassName', Folder::class); foreach ($files as $file) { - $fromPath = __DIR__ . '/ImageTest/' . $file->Name; + $fromPath = __DIR__ . '/DataDifferencerTest/images/' . $file->Name; $destPath = TestAssetStore::getLocalPath($file); // Only correct for test asset store Filesystem::makeFolder(dirname($destPath)); copy($fromPath, $destPath); diff --git a/tests/php/ORM/ImageTest/test-image-high-quality.jpg b/tests/php/ORM/DataDifferencerTest/images/test-image-high-quality.jpg similarity index 100% rename from tests/php/ORM/ImageTest/test-image-high-quality.jpg rename to tests/php/ORM/DataDifferencerTest/images/test-image-high-quality.jpg diff --git a/tests/php/ORM/ImageTest/test-image-low-quality.jpg b/tests/php/ORM/DataDifferencerTest/images/test-image-low-quality.jpg similarity index 100% rename from tests/php/ORM/ImageTest/test-image-low-quality.jpg rename to tests/php/ORM/DataDifferencerTest/images/test-image-low-quality.jpg diff --git a/tests/php/ORM/ImageTest/test-image.png b/tests/php/ORM/DataDifferencerTest/images/test-image.png similarity index 100% rename from tests/php/ORM/ImageTest/test-image.png rename to tests/php/ORM/DataDifferencerTest/images/test-image.png diff --git a/tests/php/ORM/ImageTest/test.image.with.dots.png b/tests/php/ORM/DataDifferencerTest/images/test.image.with.dots.png similarity index 100% rename from tests/php/ORM/ImageTest/test.image.with.dots.png rename to tests/php/ORM/DataDifferencerTest/images/test.image.with.dots.png diff --git a/tests/php/ORM/GDImageTest.php b/tests/php/ORM/GDImageTest.php deleted file mode 100644 index f6169156d..000000000 --- a/tests/php/ORM/GDImageTest.php +++ /dev/null @@ -1,40 +0,0 @@ -markTestSkipped("The GD extension is required"); - return; - } - - /** - * @skipUpgrade -*/ - Config::inst()->update( - 'SilverStripe\\Core\\Injector\\Injector', - 'Image_Backend', - 'SilverStripe\\Assets\\GDBackend' - ); - } - - public function tearDown() - { - $cache = Injector::inst()->get(CacheInterface::class . '.GDBackend_Manipulations'); - $cache->clear(); - - parent::tearDown(); - } -} diff --git a/tests/php/ORM/ImageTest.php b/tests/php/ORM/ImageTest.php deleted file mode 100644 index 9808420f2..000000000 --- a/tests/php/ORM/ImageTest.php +++ /dev/null @@ -1,328 +0,0 @@ -markTestSkipped(sprintf('Skipping %s ', get_class($this))); - return; - } - - // Set backend root to /ImageTest - TestAssetStore::activate('ImageTest'); - - // Copy test images for each of the fixture references - $files = File::get()->exclude('ClassName', Folder::class); - foreach ($files as $image) { - $filePath = TestAssetStore::getLocalPath($image); // Only correct for test asset store - $sourcePath = __DIR__ . '/ImageTest/' . $image->Name; - if (!file_exists($filePath)) { - SSFilesystem::makeFolder(dirname($filePath)); - if (!copy($sourcePath, $filePath)) { - user_error('Failed to copy test images', E_USER_ERROR); - } - } - } - } - - public function tearDown() - { - TestAssetStore::reset(); - parent::tearDown(); - } - - public function testGetTagWithTitle() - { - Config::inst()->update(DBFile::class, 'force_resample', false); - - $image = $this->objFromFixture(Image::class, 'imageWithTitle'); - $expected = 'This is a image Title'; - $actual = trim($image->getTag()); - - $this->assertEquals($expected, $actual); - } - - public function testGetTagWithoutTitle() - { - Config::inst()->update(DBFile::class, 'force_resample', false); - - $image = $this->objFromFixture(Image::class, 'imageWithoutTitle'); - $expected = 'test image'; - $actual = trim($image->getTag()); - - $this->assertEquals($expected, $actual); - } - - public function testGetTagWithoutTitleContainingDots() - { - Config::inst()->update(DBFile::class, 'force_resample', false); - - $image = $this->objFromFixture(Image::class, 'imageWithoutTitleContainingDots'); - $expected = 'test.image.with.dots'; - $actual = trim($image->getTag()); - - $this->assertEquals($expected, $actual); - } - - /** - * Tests that multiple image manipulations may be performed on a single Image - */ - public function testMultipleGenerateManipulationCalls() - { - $image = $this->objFromFixture(Image::class, 'imageWithoutTitle'); - - $imageFirst = $image->ScaleWidth(200); - $this->assertNotNull($imageFirst); - $expected = 200; - $actual = $imageFirst->getWidth(); - - $this->assertEquals($expected, $actual); - - $imageSecond = $imageFirst->ScaleHeight(100); - $this->assertNotNull($imageSecond); - $expected = 100; - $actual = $imageSecond->getHeight(); - $this->assertEquals($expected, $actual); - } - - /** - * Tests that image manipulations that do not affect the resulting dimensions - * of the output image do not resample the file. - */ - public function testReluctanceToResampling() - { - $image = $this->objFromFixture(Image::class, 'imageWithoutTitle'); - $this->assertTrue($image->isSize(300, 300)); - - // Set width to 300 pixels - $imageScaleWidth = $image->ScaleWidth(300); - $this->assertEquals($imageScaleWidth->getWidth(), 300); - $this->assertEquals($image->Filename, $imageScaleWidth->Filename); - - // Set height to 300 pixels - $imageScaleHeight = $image->ScaleHeight(300); - $this->assertEquals($imageScaleHeight->getHeight(), 300); - $this->assertEquals($image->Filename, $imageScaleHeight->Filename); - - // Crop image to 300 x 300 - $imageCropped = $image->Fill(300, 300); - $this->assertTrue($imageCropped->isSize(300, 300)); - $this->assertEquals($image->Filename, $imageCropped->Filename); - - // Resize (padded) to 300 x 300 - $imageSized = $image->Pad(300, 300); - $this->assertTrue($imageSized->isSize(300, 300)); - $this->assertEquals($image->Filename, $imageSized->Filename); - - // Padded image 300 x 300 (same as above) - $imagePadded = $image->Pad(300, 300); - $this->assertTrue($imagePadded->isSize(300, 300)); - $this->assertEquals($image->Filename, $imagePadded->Filename); - - // Resized (stretched) to 300 x 300 - $imageStretched = $image->ResizedImage(300, 300); - $this->assertTrue($imageStretched->isSize(300, 300)); - $this->assertEquals($image->Filename, $imageStretched->Filename); - - // Fit (various options) - $imageFit = $image->Fit(300, 600); - $this->assertTrue($imageFit->isSize(300, 300)); - $this->assertEquals($image->Filename, $imageFit->Filename); - $imageFit = $image->Fit(600, 300); - $this->assertTrue($imageFit->isSize(300, 300)); - $this->assertEquals($image->Filename, $imageFit->Filename); - $imageFit = $image->Fit(300, 300); - $this->assertTrue($imageFit->isSize(300, 300)); - $this->assertEquals($image->Filename, $imageFit->Filename); - } - - /** - * Tests that a URL to a resampled image is provided when force_resample is - * set to true, if the resampled file is smaller than the original. - */ - public function testForceResample() - { - $imageHQ = $this->objFromFixture(Image::class, 'highQualityJPEG'); - $imageHQR = $imageHQ->Resampled(); - $imageLQ = $this->objFromFixture(Image::class, 'lowQualityJPEG'); - $imageLQR = $imageLQ->Resampled(); - - // Test resampled file is served when force_resample = true - Config::inst()->update(DBFile::class, 'force_resample', true); - $this->assertLessThan($imageHQ->getAbsoluteSize(), $imageHQR->getAbsoluteSize(), 'Resampled image is smaller than original'); - $this->assertEquals($imageHQ->getURL(), $imageHQR->getSourceURL(), 'Path to a resampled image was returned by getURL()'); - - // Test original file is served when force_resample = true but original file is low quality - $this->assertGreaterThanOrEqual($imageLQ->getAbsoluteSize(), $imageLQR->getAbsoluteSize(), 'Resampled image is larger or same size as original'); - $this->assertNotEquals($imageLQ->getURL(), $imageLQR->getSourceURL(), 'Path to the original image file was returned by getURL()'); - - // Test original file is served when force_resample = false - Config::inst()->update(DBFile::class, 'force_resample', false); - $this->assertNotEquals($imageHQ->getURL(), $imageHQR->getSourceURL(), 'Path to the original image file was returned by getURL()'); - } - - public function testImageResize() - { - $image = $this->objFromFixture(Image::class, 'imageWithoutTitle'); - $this->assertTrue($image->isSize(300, 300)); - - // Test normal resize - $resized = $image->Pad(150, 100); - $this->assertTrue($resized->isSize(150, 100)); - - // Test cropped resize - $cropped = $image->Fill(100, 200); - $this->assertTrue($cropped->isSize(100, 200)); - - // Test padded resize - $padded = $image->Pad(200, 100); - $this->assertTrue($padded->isSize(200, 100)); - - // Test Fit - $ratio = $image->Fit(80, 160); - $this->assertTrue($ratio->isSize(80, 80)); - - // Test FitMax - $fitMaxDn = $image->FitMax(200, 100); - $this->assertTrue($fitMaxDn->isSize(100, 100)); - $fitMaxUp = $image->FitMax(500, 400); - $this->assertTrue($fitMaxUp->isSize(300, 300)); - - //Test ScaleMax - $scaleMaxWDn = $image->ScaleMaxWidth(200); - $this->assertTrue($scaleMaxWDn->isSize(200, 200)); - $scaleMaxWUp = $image->ScaleMaxWidth(400); - $this->assertTrue($scaleMaxWUp->isSize(300, 300)); - $scaleMaxHDn = $image->ScaleMaxHeight(200); - $this->assertTrue($scaleMaxHDn->isSize(200, 200)); - $scaleMaxHUp = $image->ScaleMaxHeight(400); - $this->assertTrue($scaleMaxHUp->isSize(300, 300)); - - // Test FillMax - $cropMaxDn = $image->FillMax(200, 100); - $this->assertTrue($cropMaxDn->isSize(200, 100)); - $cropMaxUp = $image->FillMax(400, 200); - $this->assertTrue($cropMaxUp->isSize(300, 150)); - - // Test Clip - $clipWDn = $image->CropWidth(200); - $this->assertTrue($clipWDn->isSize(200, 300)); - $clipWUp = $image->CropWidth(400); - $this->assertTrue($clipWUp->isSize(300, 300)); - $clipHDn = $image->CropHeight(200); - $this->assertTrue($clipHDn->isSize(300, 200)); - $clipHUp = $image->CropHeight(400); - $this->assertTrue($clipHUp->isSize(300, 300)); - } - - /** - * @expectedException InvalidArgumentException - */ - public function testGenerateImageWithInvalidParameters() - { - $image = $this->objFromFixture(Image::class, 'imageWithoutTitle'); - $image->ScaleHeight('String'); - $image->Pad(600, 600, 'XXXXXX'); - } - - public function testCacheFilename() - { - $image = $this->objFromFixture(Image::class, 'imageWithoutTitle'); - $imageFirst = $image->Pad(200, 200, 'CCCCCC'); - $imageFilename = $imageFirst->getURL(); - // Encoding of the arguments is duplicated from cacheFilename - $neededPart = 'Pad' . Convert::base64url_encode(array(200,200,'CCCCCC')); - $this->assertContains($neededPart, $imageFilename, 'Filename for cached image is correctly generated'); - } - - /** - * Test that propertes from the source Image are inherited by resampled images - */ - public function testPropertyInheritance() - { - $testString = 'This is a test'; - $origImage = $this->objFromFixture(Image::class, 'imageWithTitle'); - $origImage->TestProperty = $testString; - $resampled = $origImage->ScaleWidth(10); - $this->assertEquals($resampled->TestProperty, $testString); - $resampled2 = $resampled->ScaleWidth(5); - $this->assertEquals($resampled2->TestProperty, $testString); - } - - public function testShortcodeHandlerFallsBackToFileProperties() - { - $image = $this->objFromFixture(Image::class, 'imageWithTitle'); - $parser = new ShortcodeParser(); - $parser->register('image', array(Image::class, 'handle_shortcode')); - - $this->assertEquals( - sprintf( - '%s', - $image->Link(), - $image->Title - ), - $parser->parse(sprintf('[image id=%d]', $image->ID)) - ); - } - - public function testShortcodeHandlerUsesShortcodeProperties() - { - $image = $this->objFromFixture(Image::class, 'imageWithTitle'); - $parser = new ShortcodeParser(); - $parser->register('image', array(Image::class, 'handle_shortcode')); - - $this->assertEquals( - sprintf( - 'Alt content', - $image->Link() - ), - $parser->parse( - sprintf( - '[image id="%d" alt="Alt content" title="Title content"]', - $image->ID - ) - ) - ); - } - - public function testShortcodeHandlerAddsDefaultAttributes() - { - $image = $this->objFromFixture(Image::class, 'imageWithoutTitle'); - $parser = new ShortcodeParser(); - $parser->register('image', array(Image::class, 'handle_shortcode')); - - $this->assertEquals( - sprintf( - '%s', - $image->Link(), - $image->Title - ), - $parser->parse( - sprintf( - '[image id="%d"]', - $image->ID - ) - ) - ); - } -} diff --git a/tests/php/ORM/ImageTest.yml b/tests/php/ORM/ImageTest.yml deleted file mode 100644 index e73229acd..000000000 --- a/tests/php/ORM/ImageTest.yml +++ /dev/null @@ -1,38 +0,0 @@ -SilverStripe\Assets\Folder: - folder1: - Name: folder -SilverStripe\Assets\Image: - imageWithTitle: - Title: This is a image Title - FileFilename: folder/test-image.png - FileHash: 444065542b5dd5187166d8e1cd684e0d724c5a97 - Parent: =>SilverStripe\Assets\Folder.folder1 - Name: test-image.png - imageWithoutTitle: - FileFilename: folder/test-image.png - FileHash: 444065542b5dd5187166d8e1cd684e0d724c5a97 - Parent: =>SilverStripe\Assets\Folder.folder1 - Name: test-image.png - imageWithoutTitleContainingDots: - FileFilename: folder/test.image.with.dots.png - FileHash: 46affab7043cfd9f1ded919dd24affd08e926eca - Parent: =>SilverStripe\Assets\Folder.folder1 - Name: test.image.with.dots.png - imageWithMetacharacters: - Title: This is a/an image Title - FileFilename: folder/test-image.png - FileHash: 444065542b5dd5187166d8e1cd684e0d724c5a97 - Parent: =>SilverStripe\Assets\Folder.folder1 - Name: test-image.png - lowQualityJPEG: - Title: This is a low quality JPEG - FileFilename: folder/test-image-low-quality.jpg - FileHash: 33be1b95cba0358fe54e8b13532162d52f97421c - Parent: =>SilverStripe\Assets\Folder.folder1 - Name: test-image-low-quality.jpg - highQualityJPEG: - Title: This is a high quality JPEG - FileFilename: folder/test-image-high-quality.jpg - FileHash: a870de278b475cb75f5d9f451439b2d378e13af1 - Parent: =>SilverStripe\Assets\Folder.folder1 - Name: test-image-high-quality.jpg diff --git a/tests/php/ORM/ImagickImageTest.php b/tests/php/ORM/ImagickImageTest.php deleted file mode 100644 index 2a3db4799..000000000 --- a/tests/php/ORM/ImagickImageTest.php +++ /dev/null @@ -1,25 +0,0 @@ -markTestSkipped("The Imagick extension is not available."); - return; - } - - /** - * @skipUpgrade -*/ - Config::inst()->update(Injector::class, 'Image_Backend', ImagickBackend::class); - } -}