diff --git a/CHANGELOG.md b/CHANGELOG.md index c5fbc920..7238e132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ###3.7.0### +- Added support for importing/exporting Asset Transforms - Asset Sources now respect the force option ###3.6.1### diff --git a/README.md b/README.md index 5a568f75..cae48c9a 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Here is a list of all of the data types and their corresponding exclude paramete | Data Type | Exlude Parameter | | ------------- |-------------| | Asset Sources | assetSources | +| Asset Transforms | assetTransforms | | Category Groups | categoryGroups | | Element Indexes | elementIndexSettings | | Fields | fields | diff --git a/src/Console/App.php b/src/Console/App.php index 46517956..0a8edbe3 100644 --- a/src/Console/App.php +++ b/src/Console/App.php @@ -313,6 +313,9 @@ private function _setSchematicComponents() 'schematic_assetSources' => [ 'class' => Service\AssetSources::class, ], + 'schematic_assetTransforms' => [ + 'class' => Service\AssetTransforms::class, + ], 'schematic_fields' => [ 'class' => Service\Fields::class, ], diff --git a/src/Models/Data.php b/src/Models/Data.php index c3a97979..ddce7ea5 100644 --- a/src/Models/Data.php +++ b/src/Models/Data.php @@ -21,6 +21,7 @@ * * @property array $Locales * @property array $assetSources + * @property array $assetTransforms * @property array $fields * @property array $globalSets * @property array $plugins @@ -44,6 +45,7 @@ protected function defineAttributes() return [ 'locales' => [AttributeType::Mixed, 'default' => []], 'assetSources' => [AttributeType::Mixed, 'default' => []], + 'assetTransforms' => [AttributeType::Mixed, 'default' => []], 'fields' => [AttributeType::Mixed, 'default' => []], 'globalSets' => [AttributeType::Mixed, 'default' => []], 'plugins' => [AttributeType::Mixed, 'default' => []], diff --git a/src/Services/AssetSources.php b/src/Services/AssetSources.php index ed6b4f04..3afa1477 100644 --- a/src/Services/AssetSources.php +++ b/src/Services/AssetSources.php @@ -7,7 +7,7 @@ use Craft\AssetSourceModel; /** - * Schematic Assets Service. + * Schematic Asset Sources Service. * * Sync Craft Setups. * diff --git a/src/Services/AssetTransforms.php b/src/Services/AssetTransforms.php new file mode 100644 index 00000000..614f50e1 --- /dev/null +++ b/src/Services/AssetTransforms.php @@ -0,0 +1,152 @@ +assetTransforms; + } + + /** + * Export all asset transforms. + * + * @param AssetTransformModel[] $assetTransforms + * + * @return array + */ + public function export(array $assetTransforms = []) + { + Craft::log(Craft::t('Exporting Asset Transforms')); + + $assetTransformDefinitions = []; + + foreach ($assetTransforms as $assetTransform) { + $assetTransformDefinitions[$assetTransform->handle] = $this->getAssetTransformDefinition($assetTransform); + } + + return $assetTransformDefinitions; + } + + /** + * @param AssetTransformModel $assetTransform + * + * @return array + */ + private function getAssetTransformDefinition(AssetTransformModel $assetTransform) + { + return [ + 'name' => $assetTransform->name, + 'width' => $assetTransform->width, + 'height' => $assetTransform->height, + 'format' => $assetTransform->format, + 'dimensionChangeTime' => $assetTransform->dimensionChangeTime, + 'mode' => $assetTransform->mode, + 'position' => $assetTransform->position, + 'quality' => $assetTransform->quality, + ]; + } + + /** + * Import asset transform definitions. + * + * @param array $assetTransformDefinitions + * @param bool $force + * + * @return Result + */ + public function import(array $assetTransformDefinitions, $force = false) + { + Craft::log(Craft::t('Importing Asset Transforms')); + + $this->resetCraftAssetTransformsServiceCache(); + $assetTransforms = $this->getAssetTransformsService()->getAllTransforms('handle'); + + foreach ($assetTransformDefinitions as $assetTransformHandle => $assetTransformDefinition) { + $assetTransform = array_key_exists($assetTransformHandle, $assetTransforms) + ? $assetTransforms[$assetTransformHandle] + : new AssetTransformModel(); + + unset($assetTransforms[$assetTransformHandle]); + + $this->populateAssetTransform($assetTransform, $assetTransformDefinition, $assetTransformHandle); + + if (!$this->getAssetTransformsService()->saveTransform($assetTransform)) { // Save asset transform via craft + $this->addErrors($assetTransform->getAllErrors()); + + continue; + } + } + + if ($force) { + foreach ($assetTransforms as $assetTransform) { + $this->getAssetTransformsService()->deleteTransform($assetTransform->id); + } + } + + return $this->getResultModel(); + } + + /** + * Populate asset transform. + * + * @param AssetTransformModel $assetTransform + * @param array $assetTransformDefinition + * @param string $assetTransformHandle + * + * @return AssetTransformModel + */ + private function populateAssetTransform(AssetTransformModel $assetTransform, array $assetTransformDefinition, $assetTransformHandle) + { + $assetTransform->setAttributes([ + 'handle' => $assetTransformHandle, + 'name' => $assetTransformDefinition['name'], + 'width' => $assetTransformDefinition['width'], + 'height' => $assetTransformDefinition['height'], + 'format' => $assetTransformDefinition['format'], + 'dimensionChangeTime' => $assetTransformDefinition['dimensionChangeTime'], + 'mode' => $assetTransformDefinition['mode'], + 'position' => $assetTransformDefinition['position'], + 'quality' => $assetTransformDefinition['quality'], + ]); + + return $assetTransform; + } + + /** + * Reset craft fields service cache using reflection. + */ + private function resetCraftAssetTransformsServiceCache() + { + $obj = $this->getAssetTransformsService(); + $refObject = new \ReflectionObject($obj); + if ($refObject->hasProperty('_fetchedAllTransforms')) { + $refProperty = $refObject->getProperty('_fetchedAllTransforms'); + $refProperty->setAccessible(true); + $refProperty->setValue($obj, false); + } + if ($refObject->hasProperty('_transformsByHandle')) { + $refProperty = $refObject->getProperty('_transformsByHandle'); + $refProperty->setAccessible(true); + $refProperty->setValue($obj, array()); + } + } +} diff --git a/src/Services/Schematic.php b/src/Services/Schematic.php index a4871d73..0ac46d51 100644 --- a/src/Services/Schematic.php +++ b/src/Services/Schematic.php @@ -28,6 +28,7 @@ class Schematic extends BaseApplication protected static $exportableDataTypes = [ 'locales', 'assetSources', + 'assetTransforms', 'fields', 'plugins', 'sections', @@ -127,6 +128,9 @@ private function importDataModel(Data $model, $force) $assetSources = $model->getAttribute('assetSources'); $assetSourcesImportResult = Craft::app()->schematic_assetSources->import($assetSources, $force); + $assetTransforms = $model->getAttribute('assetTransforms'); + $assetTransformsImportResult = Craft::app()->schematic_assetTransforms->import($assetTransforms, $force); + $globalSets = $model->getAttribute('globalSets'); $globalSetsImportResult = Craft::app()->schematic_globalSets->import($globalSets, $force); @@ -162,6 +166,7 @@ private function importDataModel(Data $model, $force) $result->consume($pluginImportResult); $result->consume($fieldImportResult); $result->consume($assetSourcesImportResult); + $result->consume($assetTransformsImportResult); $result->consume($globalSetsImportResult); $result->consume($sectionImportResult); $result->consume($categoryGroupImportResult); @@ -228,6 +233,7 @@ private function exportDataModel($dataTypes = 'all') } $assetSources = Craft::app()->assetSources->getAllSources(); + $assetTransforms = Craft::app()->assetTransforms->getAllTransforms(); $categoryGroups = Craft::app()->categories->getAllGroups(); $tagGroups = Craft::app()->tags->getAllTagGroups(); @@ -241,6 +247,10 @@ private function exportDataModel($dataTypes = 'all') $export['assetSources'] = Craft::app()->schematic_assetSources->export($assetSources); } + if (in_array('assetTransforms', $dataTypes)) { + $export['assetTransforms'] = Craft::app()->schematic_assetTransforms->export($assetTransforms); + } + if (in_array('fields', $dataTypes)) { $fieldGroups = Craft::app()->fields->getAllGroups(); $export['fields'] = Craft::app()->schematic_fields->export($fieldGroups); diff --git a/tests/Services/AssetTransformsTest.php b/tests/Services/AssetTransformsTest.php new file mode 100644 index 00000000..e7061654 --- /dev/null +++ b/tests/Services/AssetTransformsTest.php @@ -0,0 +1,258 @@ + + */ +class AssetTransformsTest extends BaseTest +{ + //============================================================================================================== + //================================================= TESTS ==================================================== + //============================================================================================================== + + /** + * @covers ::export + * @dataProvider provideValidAssetTransforms + * + * @param AssetTransformModel[] $assetTransforms + * @param array $expectedResult + */ + public function testSuccessfulExport(array $assetTransforms, array $expectedResult = []) + { + $schematicAssetTransformsService = new AssetTransforms(); + + $actualResult = $schematicAssetTransformsService->export($assetTransforms); + + $this->assertSame($expectedResult, $actualResult); + } + + /** + * @covers ::import + * @dataProvider provideValidAssetTransformDefinitions + * + * @param array $assetTransformDefinitions + */ + public function testSuccessfulImport(array $assetTransformDefinitions) + { + $this->setMockAssetTransformsService(); + $this->setMockDbConnection(); + + $schematicAssetTransformsService = new AssetTransforms(); + + $import = $schematicAssetTransformsService->import($assetTransformDefinitions); + + $this->assertInstanceOf(Result::class, $import); + $this->assertFalse($import->hasErrors()); + } + + /** + * @covers ::import + * @dataProvider provideValidAssetTransformDefinitions + * + * @param array $assetTransformDefinitions + */ + public function testImportWithForceOption(array $assetTransformDefinitions) + { + $this->setMockAssetTransformsService(); + $this->setMockDbConnection(); + + $schematicAssetTransformsService = new AssetTransforms(); + + $import = $schematicAssetTransformsService->import($assetTransformDefinitions, true); + + $this->assertInstanceOf(Result::class, $import); + $this->assertFalse($import->hasErrors()); + } + + //============================================================================================================== + //============================================== PROVIDERS =================================================== + //============================================================================================================== + + /** + * @return array + */ + public function provideValidAssetTransforms() + { + return [ + 'emptyArray' => [ + 'AssetTransforms' => [], + 'expectedResult' => [], + ], + 'single asset source' => [ + 'AssetTransforms' => [ + 'assetTransform1' => $this->getMockAssetTransform(1), + ], + 'expectedResult' => [ + 'assetTransformHandle1' => [ + 'name' => 'assetTransformName1', + 'width' => null, + 'height' => null, + 'format' => null, + 'dimensionChangeTime' => null, + 'mode' => null, + 'position' => null, + 'quality' => null, + ], + ], + ], + 'multiple asset sources' => [ + 'AssetTransforms' => [ + 'assetTransform1' => $this->getMockAssetTransform(1), + 'assetTransform2' => $this->getMockAssetTransform(2), + ], + 'expectedResult' => [ + 'assetTransformHandle1' => [ + 'name' => 'assetTransformName1', + 'width' => null, + 'height' => null, + 'format' => null, + 'dimensionChangeTime' => null, + 'mode' => null, + 'position' => null, + 'quality' => null, + ], + 'assetTransformHandle2' => [ + 'name' => 'assetTransformName2', + 'width' => null, + 'height' => null, + 'format' => null, + 'dimensionChangeTime' => null, + 'mode' => null, + 'position' => null, + 'quality' => null, + ], + ], + ], + ]; + } + + /** + * @return array + */ + public function provideValidAssetTransformDefinitions() + { + return [ + 'emptyArray' => [ + 'assetTransformDefinitions' => [], + ], + 'single group' => [ + 'assetTransformDefinitions' => [ + 'assetTransformHandle1' => [ + 'name' => 'assetTransformName1', + 'width' => 100, + 'height' => 100, + 'format' => 'jpg', + 'dimensionChangeTime' => null, + 'mode' => 'crop', + 'position' => 'center-center', + 'quality' => 75, + ], + ], + ], + ]; + } + + //============================================================================================================== + //================================================= MOCKS ==================================================== + //============================================================================================================== + + /** + * @param string $assetTransformId + * + * @return Mock|AssetTransformModel + */ + private function getMockAssetTransform($assetTransformId) + { + $mockAssetTransform = $this->getMockBuilder(AssetTransformModel::class) + ->disableOriginalConstructor() + ->getMock(); + + $mockAssetTransform->expects($this->any()) + ->method('__get') + ->willReturnMap([ + ['id', $assetTransformId], + ['handle', 'assetTransformHandle'.$assetTransformId], + ['name', 'assetTransformName'.$assetTransformId], + ]); + + $mockAssetTransform->expects($this->any()) + ->method('getAllErrors') + ->willReturn([ + 'ohnoes' => 'horrible error', + ]); + + return $mockAssetTransform; + } + + /** + * @return Mock|AssetTransformsService + */ + private function setMockAssetTransformsService() + { + $mockAssetTransformsService = $this->getMockBuilder(AssetTransformsService::class) + ->disableOriginalConstructor() + ->setMethods(['getAllTransforms', 'saveTransform', 'deleteTransform']) + ->getMock(); + + $mockAssetTransformsService->expects($this->any()) + ->method('getAllTransforms') + ->with('handle') + ->willReturn([]); + + $this->setComponent(Craft::app(), 'assetTransforms', $mockAssetTransformsService); + + return $mockAssetTransformsService; + } + + /** + * @return Mock|DbConnection + */ + private function setMockDbConnection() + { + $mockDbConnection = $this->getMockBuilder(DbConnection::class) + ->disableOriginalConstructor() + ->setMethods(['createCommand']) + ->getMock(); + $mockDbConnection->autoConnect = false; // Do not auto connect + + $mockDbCommand = $this->getMockDbCommand(); + $mockDbConnection->expects($this->any())->method('createCommand')->willReturn($mockDbCommand); + + Craft::app()->setComponent('db', $mockDbConnection); + + return $mockDbConnection; + } + + /** + * @return Mock|DbCommand + */ + private function getMockDbCommand() + { + $mockDbCommand = $this->getMockBuilder(DbCommand::class) + ->disableOriginalConstructor() + ->setMethods(['insertOrUpdate']) + ->getMock(); + + return $mockDbCommand; + } +} diff --git a/tests/Services/SchematicTest.php b/tests/Services/SchematicTest.php index badb6eee..68d3540f 100644 --- a/tests/Services/SchematicTest.php +++ b/tests/Services/SchematicTest.php @@ -4,6 +4,7 @@ use Craft\BaseTest; use Craft\AssetSourcesService; +use Craft\AssetTransformsService; use Craft\CategoriesService; use Craft\Craft; use Craft\FieldsService; @@ -205,6 +206,20 @@ public function getMockAssetSourcesService() return $mock; } + /** + * @return Mock|AssetTransformsService + */ + public function getMockAssetTransformsService() + { + $mock = $this->getMockBuilder(AssetTransformsService::class) + ->disableOriginalConstructor() + ->getMock(); + + $mock->expects($this->exactly(1))->method('getAllTransforms')->willReturn([]); + + return $mock; + } + /** * @return Mock|CategoriesService */ @@ -240,6 +255,7 @@ private function mockServices() { $this->createMockService(Locales::class, 'schematic_locales'); $this->createMockService(AssetSources::class, 'schematic_assetSources'); + $this->createMockService(AssetTransforms::class, 'schematic_assetTransforms'); $this->createMockService(Fields::class, 'schematic_fields'); $this->createMockService(GlobalSets::class, 'schematic_globalSets'); $this->createMockService(Plugins::class, 'schematic_plugins'); @@ -298,6 +314,9 @@ private function prepExportMockServices() $mockAssetSourcesService = $this->getMockAssetSourcesService(); $this->setCraftComponent('assetSources', $mockAssetSourcesService); + $mockAssetTransformsService = $this->getMockAssetTransformsService(); + $this->setCraftComponent('assetTransforms', $mockAssetTransformsService); + $mockCategoriesService = $this->getMockCategoriesService(); $this->setCraftComponent('categories', $mockCategoriesService);