diff --git a/README.md b/README.md index 695f22b9..92118b0e 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,9 @@ This project has been licensed under the MIT License (MIT). Please see [License ## Changelog +###3.4.0### +- Schematic now also exports and imports tag groups + ###3.3.2### - Allow 'singles' as a source diff --git a/src/Console/App.php b/src/Console/App.php index 0a4b0cc4..fa9d6231 100644 --- a/src/Console/App.php +++ b/src/Console/App.php @@ -325,6 +325,9 @@ private function _setSchematicComponents() 'schematic_categoryGroups' => [ 'class' => Service\CategoryGroups::class, ], + 'schematic_tagGroups' => [ + 'class' => Service\TagGroups::class, + ], ]; // Element index settings are supported from Craft 2.5 diff --git a/src/Models/Data.php b/src/Models/Data.php index 6740dd57..50d02609 100644 --- a/src/Models/Data.php +++ b/src/Models/Data.php @@ -30,6 +30,7 @@ * @property array $elementIndexSettings * @property array $pluginData * @property array $categoryGroups + * @property array $tagGroups */ class Data extends Base { @@ -52,6 +53,7 @@ protected function defineAttributes() 'elementIndexSettings' => [AttributeType::Mixed, 'default' => []], 'pluginData' => [AttributeType::Mixed, 'default' => []], 'categoryGroups' => [AttributeType::Mixed, 'default' => []], + 'tagGroups' => [AttributeType::Mixed, 'default' => []], ]; } diff --git a/src/Models/Field.php b/src/Models/Field.php index e6957e3a..c2f5ffe3 100644 --- a/src/Models/Field.php +++ b/src/Models/Field.php @@ -27,38 +27,6 @@ protected function getFieldFactory() return Craft::app()->schematic_fields->getFieldFactory(); } - /** - * @return SectionsService - */ - private function getSectionsService() - { - return Craft::app()->sections; - } - - /** - * @return UserGroupsService - */ - private function getUserGroupsService() - { - return Craft::app()->userGroups; - } - - /** - * @return AssetSources - */ - private function getAssetSourcesService() - { - return Craft::app()->schematic_assetSources; - } - - /** - * @return CategoriesService - */ - private function getCategoriesService() - { - return Craft::app()->categories; - } - /** * @param FieldModel $field * @param $includeContext @@ -161,7 +129,7 @@ private function getMappedSources($fieldType, $sources, $indexFrom, $indexTo) */ private function getSource($fieldType, $source, $indexFrom, $indexTo) { - if ($source == 'singles') { + if ($source == 'singles' || $source == '*') { return $source; } @@ -172,23 +140,27 @@ private function getSource($fieldType, $source, $indexFrom, $indexTo) list($sourceType, $sourceFrom) = explode(':', $source); switch ($sourceType) { case 'section': - $service = $this->getSectionsService(); + $service = Craft::app()->sections; $method = 'getSectionBy'; break; case 'group': - $service = $fieldType == 'Users' ? $this->getUserGroupsService() : $this->getCategoriesService(); + $service = $fieldType == 'Users' ? Craft::app()->userGroups : Craft::app()->categories; $method = 'getGroupBy'; break; case 'folder': - $service = $this->getAssetSourcesService(); + $service = Craft::app()->assetSources; $method = 'getSourceTypeBy'; break; + case 'taggroup': + $service = Craft::app()->tags; + $method = 'getTagGroupBy'; + break; } } elseif ($source !== 'singles') { //Backwards compatibility $sourceType = 'section'; $sourceFrom = $source; - $service = $this->getSectionsService(); + $service = Craft::app()->sections; $method = 'getSectionBy'; } diff --git a/src/Services/Schematic.php b/src/Services/Schematic.php index e767af9b..28e0b881 100644 --- a/src/Services/Schematic.php +++ b/src/Services/Schematic.php @@ -101,6 +101,7 @@ private function importDataModel(Data $model, $force) $globalSetsImportResult = Craft::app()->schematic_globalSets->import($model->getAttribute('globalSets'), $force); $sectionImportResult = Craft::app()->schematic_sections->import($model->getAttribute('sections'), $force); $categoryGroupImportResult = Craft::app()->schematic_categoryGroups->import($model->getAttribute('categoryGroups'), $force); + $tagGroupImportResult = Craft::app()->schematic_tagGroups->import($model->getAttribute('tagGroups'), $force); $userGroupImportResult = Craft::app()->schematic_userGroups->import($model->getAttribute('userGroups'), $force); $userImportResult = Craft::app()->schematic_users->import($model->getAttribute('users'), true); $fieldImportResultFinal = Craft::app()->schematic_fields->import($model->getAttribute('fields'), $force); @@ -119,6 +120,7 @@ private function importDataModel(Data $model, $force) $result->consume($globalSetsImportResult); $result->consume($sectionImportResult); $result->consume($categoryGroupImportResult); + $result->consume($tagGroupImportResult); $result->consume($userGroupImportResult); $result->consume($userImportResult); $result->consume($fieldImportResultFinal); @@ -167,6 +169,7 @@ private function exportDataModel() $globals = Craft::app()->globals->getAllSets(); $userGroups = Craft::app()->userGroups->getAllGroups(); $categoryGroups = Craft::app()->categories->getAllGroups(); + $tagGroups = Craft::app()->tags->getAllTagGroups(); $export = [ 'locales' => Craft::app()->schematic_locales->export(), @@ -178,6 +181,7 @@ private function exportDataModel() 'userGroups' => Craft::app()->schematic_userGroups->export($userGroups), 'users' => Craft::app()->schematic_users->export(), 'categoryGroups' => Craft::app()->schematic_categoryGroups->export($categoryGroups), + 'tagGroups' => Craft::app()->schematic_tagGroups->export($tagGroups), ]; // Element index settings are supported from Craft 2.5 diff --git a/src/Services/TagGroups.php b/src/Services/TagGroups.php new file mode 100644 index 00000000..156314fb --- /dev/null +++ b/src/Services/TagGroups.php @@ -0,0 +1,112 @@ +handle] = $this->getTagGroupDefinition($tagGroup); + } + + return $tagGroupDefinitions; + } + + /** + * Get tagGroup definition. + * + * @param TagGroupModel $tagGroup + * + * @return array + */ + private function getTagGroupDefinition(TagGroupModel $tagGroup) + { + return [ + 'name' => $tagGroup->name, + 'fieldLayout' => Craft::app()->schematic_fields->getFieldLayoutDefinition($tagGroup->getFieldLayout()), + ]; + } + + /** + * Attempt to import tagGroups. + * + * @param array $tagGroupDefinitions + * @param bool $force If set to true tagGroups not included in the import will be deleted + * + * @return Result + */ + public function import(array $tagGroupDefinitions, $force = false) + { + Craft::log(Craft::t('Importing TagGroups')); + + $tagGroups = Craft::app()->tags->getAllTagGroups('handle'); + + foreach ($tagGroupDefinitions as $tagGroupHandle => $tagGroupDefinition) { + $tagGroup = array_key_exists($tagGroupHandle, $tagGroups) + ? $tagGroups[$tagGroupHandle] + : new TagGroupModel(); + + unset($tagGroups[$tagGroupHandle]); + + $this->populateTagGroup($tagGroup, $tagGroupDefinition, $tagGroupHandle); + + if (!Craft::app()->tags->saveTagGroup($tagGroup)) { // Save taggroup via craft + $this->addErrors($tagGroup->getAllErrors()); + + continue; + } + } + + if ($force) { + foreach ($tagGroups as $tagGroup) { + Craft::app()->tags->deleteTagGroupById($tagGroup->id); + } + } + + return $this->getResultModel(); + } + + /** + * Populate taggroup. + * + * @param TagGroupModel $tagGroup + * @param array $tagGroupDefinition + * @param string $tagGroupHandle + */ + private function populateTagGroup(TagGroupModel $tagGroup, array $tagGroupDefinition, $tagGroupHandle) + { + $tagGroup->setAttributes([ + 'handle' => $tagGroupHandle, + 'name' => $tagGroupDefinition['name'], + ]); + + $fieldLayout = Craft::app()->schematic_fields->getFieldLayout($tagGroupDefinition['fieldLayout']); + $tagGroup->setFieldLayout($fieldLayout); + } +} diff --git a/tests/Services/SchematicTest.php b/tests/Services/SchematicTest.php index a29b7a39..1ed996c7 100644 --- a/tests/Services/SchematicTest.php +++ b/tests/Services/SchematicTest.php @@ -2,13 +2,14 @@ namespace NerdsAndCompany\Schematic\Services; +use Craft\BaseTest; use Craft\CategoriesService; use Craft\Craft; -use Craft\BaseTest; use Craft\FieldsService; use Craft\GlobalsService; -use Craft\SectionsService; use Craft\PluginsService; +use Craft\SectionsService; +use Craft\TagsService; use Craft\UserGroupsService; use NerdsAndCompany\Schematic\Models\Result; use PHPUnit_Framework_MockObject_MockObject as Mock; @@ -201,6 +202,20 @@ public function getMockCategoriesService() return $mock; } + /** + * @return Mock|TagsService + */ + public function getMockTagsService() + { + $mock = $this->getMockBuilder(TagsService::class) + ->disableOriginalConstructor() + ->getMock(); + + $mock->expects($this->exactly(1))->method('getAllTagGroups')->willReturn([]); + + return $mock; + } + /** * Mock all required services. */ @@ -215,6 +230,7 @@ private function mockServices() $this->createMockService(UserGroups::class, 'schematic_userGroups'); $this->createMockService(Users::class, 'schematic_users'); $this->createMockService(CategoryGroups::class, 'schematic_categoryGroups'); + $this->createMockService(TagGroups::class, 'schematic_tagGroups'); $this->createMockService(ElementIndexSettings::class, 'schematic_elementIndexSettings'); $mockPluginsService = $this->getMockPluginsService(); @@ -264,6 +280,9 @@ private function prepExportMockServices() $mockCategoriesService = $this->getMockCategoriesService(); $this->setCraftComponent('categories', $mockCategoriesService); + + $mockTagsService = $this->getMockTagsService(); + $this->setCraftComponent('tags', $mockTagsService); } /** diff --git a/tests/Services/TagGroupsTest.php b/tests/Services/TagGroupsTest.php new file mode 100644 index 00000000..5dd360cb --- /dev/null +++ b/tests/Services/TagGroupsTest.php @@ -0,0 +1,313 @@ + + */ +class TagGroupsTest extends BaseTest +{ + //============================================================================================================== + //================================================= TESTS ==================================================== + //============================================================================================================== + + /** + * @covers ::export + * @dataProvider provideValidTagGroups + * + * @param TagGroupModel[] $groups + * @param array $expectedResult + */ + public function testSuccessfulExport(array $groups, array $expectedResult = []) + { + $this->setMockFieldsService(); + $this->setMockSchematicFields(); + + $schematicTagGroupsService = new TagGroups(); + + $actualResult = $schematicTagGroupsService->export($groups); + + $this->assertSame($expectedResult, $actualResult); + } + + /** + * @covers ::import + * @dataProvider provideValidTagGroupDefinitions + * + * @param array $groupDefinitions + */ + public function testSuccessfulImport(array $groupDefinitions) + { + $this->setMockTagsService(); + $this->setMockDbConnection(); + $this->setMockSchematicFields(); + + $schematicUserGroupsService = new TagGroups(); + + $import = $schematicUserGroupsService->import($groupDefinitions); + + $this->assertInstanceOf(Result::class, $import); + $this->assertFalse($import->hasErrors()); + } + + /** + * @covers ::import + * @dataProvider provideValidTagGroupDefinitions + * + * @param array $groupDefinitions + */ + public function testImportWithForceOption(array $groupDefinitions) + { + $this->setMockTagsService(); + $this->setMockDbConnection(); + $this->setMockSchematicFields(); + + $schematicUserGroupsService = new TagGroups(); + + $import = $schematicUserGroupsService->import($groupDefinitions, true); + + $this->assertInstanceOf(Result::class, $import); + $this->assertFalse($import->hasErrors()); + } + + //============================================================================================================== + //============================================== PROVIDERS =================================================== + //============================================================================================================== + + /** + * @return array + */ + public function provideValidTagGroups() + { + return [ + 'emptyArray' => [ + 'TagGroups' => [], + 'expectedResult' => [], + ], + 'single group' => [ + 'TagGroups' => [ + 'group1' => $this->getMockTagGroup(1), + ], + 'expectedResult' => [ + 'groupHandle1' => [ + 'name' => 'groupName1', + 'fieldLayout' => [ + 'fields' => [] + ], + ], + ], + ], + 'multiple groups' => [ + 'TagGroups' => [ + 'group1' => $this->getMockTagGroup(1), + 'group2' => $this->getMockTagGroup(2), + ], + 'expectedResult' => [ + 'groupHandle1' => [ + 'name' => 'groupName1', + 'fieldLayout' => [ + 'fields' => [] + ], + ], + 'groupHandle2' => [ + 'name' => 'groupName2', + 'fieldLayout' => [ + 'fields' => [] + ], + ], + ], + ], + ]; + } + + /** + * @return array + */ + public function provideValidTagGroupDefinitions() + { + return [ + 'emptyArray' => [ + 'groupDefinitions' => [], + ], + 'single group' => [ + 'groupDefinitions' => [ + 'groupHandle1' => [ + 'name' => 'groupName1', + 'fieldLayout' => [ + 'fields' => [] + ], + ], + ], + ], + ]; + } + + //============================================================================================================== + //================================================= MOCKS ==================================================== + //============================================================================================================== + + /** + * @param string $groupId + * + * @return Mock|TagGroupModel + */ + private function getMockTagGroup($groupId) + { + $mockTagGroup = $this->getMockBuilder(TagGroupModel::class) + ->setMethods(['__get', 'getAllErrors', 'getFieldLayout']) + //->disableOriginalConstructor() + ->getMock(); + + $mockTagGroup->expects($this->any()) + ->method('__get') + ->willReturnMap([ + ['id', $groupId], + ['fieldLayoutId', $groupId], + ['handle', 'groupHandle' . $groupId], + ['name', 'groupName' . $groupId], + ]); + + $mockTagGroup->expects($this->any()) + ->method('getAllErrors') + ->willReturn([ + 'ohnoes' => 'horrible error', + ]); + + $mockTagGroup->expects($this->any()) + ->method('getFieldLayout') + ->willReturn($this->getMockFieldLayout()); + + return $mockTagGroup; + } + + /** + * @return Mock|CraftFieldsService + */ + private function setMockFieldsService() + { + $mockFieldsService = $this->getMockBuilder(FieldsService::class) + ->disableOriginalConstructor() + ->getMock(); + + $mockFieldsService->expects($this->any()) + ->method('getLayoutById') + ->with($this->isType('integer')) + ->willReturn($this->getMockFieldLayout()); + + $this->setComponent(Craft::app(), 'fields', $mockFieldsService); + + return $mockFieldsService; + } + + /** + * @return Mock|fields + */ + private function setMockSchematicFields() + { + $mockSchematicFields = $this->getMockBuilder(Fields::class) + ->disableOriginalConstructor() + ->getMock(); + + $mockSchematicFields->expects($this->any()) + ->method('getFieldLayoutDefinition') + ->with($this->isInstanceOf(FieldLayoutModel::class)) + ->willReturn(['fields' => []]); + + $mockSchematicFields->expects($this->any()) + ->method('getFieldLayout') + ->with($this->isType('array')) + ->willReturn($this->getMockFieldLayout()); + + $this->setComponent(Craft::app(), 'schematic_fields', $mockSchematicFields); + + return $mockSchematicFields; + } + + /** + * @return Mock|TagsService + */ + private function setMockTagsService() + { + $mockTagsService = $this->getMockBuilder(TagsService::class) + ->disableOriginalConstructor() + ->setMethods(['getAllTagGroups', 'saveTagGroup', 'deleteTagGroupById']) + ->getMock(); + + $mockTagsService->expects($this->any()) + ->method('getAllTagGroups') + ->with('handle') + ->willReturn([]); + + $this->setComponent(Craft::app(), 'tags', $mockTagsService); + + return $mockTagsService; + } + + + /** + * @return Mock|FieldLayoutModel + */ + private function getMockFieldLayout() + { + $mockFieldLayout = $this->getMockBuilder(FieldLayoutModel::class) + ->disableOriginalConstructor() + ->getMock(); + + return $mockFieldLayout; + } + + /** + * @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; + } +}