diff --git a/Model/Component/AttributeSets.php b/Model/Component/AttributeSets.php
new file mode 100644
index 0000000..9bca925
--- /dev/null
+++ b/Model/Component/AttributeSets.php
@@ -0,0 +1,148 @@
+eavSetup = $eavSetup;
+ $this->attributeSetRepository = $attributeSetRepository;
+ }
+
+ /**
+ * @param array $attributeConfigurationData
+ */
+ protected function processData($attributeConfigurationData = null)
+ {
+ try {
+ foreach ($attributeConfigurationData['attribute_sets'] as $attributeSetConfiguration) {
+ $this->processAttributeSet($attributeSetConfiguration);
+ }
+ } catch (ComponentException $e) {
+ $this->log->logError($e->getMessage());
+ }
+ }
+
+ /**
+ * @param array $attributeSetConfig
+ */
+ protected function processAttributeSet(array $attributeSetConfig)
+ {
+ $this->eavSetup->addAttributeSet(Product::ENTITY, $attributeSetConfig['name']);
+
+ $attributeSetId = $this->eavSetup->getAttributeSetId(Product::ENTITY, $attributeSetConfig['name']);
+ $attributeSetEntity = $this->attributeSetRepository->get($attributeSetId);
+ if (array_key_exists('inherit', $attributeSetConfig)) {
+ $attributeSetEntity->initFromSkeleton($this->getAttributeSetId($attributeSetConfig['inherit']));
+ $this->attributeSetRepository->save($attributeSetEntity);
+ }
+
+ if (array_key_exists('groups', $attributeSetConfig) && count($attributeSetConfig['groups']) > 0) {
+ $this->addAttributeGroups($attributeSetEntity, $attributeSetConfig['groups']);
+ $this->addAttributeGroupAssociations($attributeSetEntity, $attributeSetConfig['groups']);
+ }
+ }
+
+ /**
+ * @param AttributeSetInterface $attributeSetEntity
+ * @param array $attributeGroupData
+ */
+ protected function addAttributeGroups(AttributeSetInterface $attributeSetEntity, array $attributeGroupData)
+ {
+ /* if ($attributeSetEntity->getDefaultGroupId()) {
+ $this->eavSetup->removeAttributeGroup(
+ Product::ENTITY,
+ $attributeSetEntity->getId(),
+ $attributeSetEntity->getDefaultGroupId()
+ );
+ }*/
+
+ foreach ($attributeGroupData as $group) {
+ $attributeSetName = $attributeSetEntity->getAttributeSetName();
+ $this->eavSetup->addAttributeGroup(Product::ENTITY, $attributeSetName, $group['name']);
+ }
+ }
+
+ /**
+ * @param AttributeSetInterface $attributeSetEntity
+ * @param array $attributeGroupData
+ */
+ protected function addAttributeGroupAssociations(
+ AttributeSetInterface $attributeSetEntity,
+ array $attributeGroupData
+ ) {
+ foreach ($attributeGroupData as $group) {
+ foreach ($group['attributes'] as $attributeCode) {
+ $attributeData = $this->eavSetup->getAttribute(Product::ENTITY, $attributeCode);
+
+ if (count($attributeData) === 0) {
+ throw new ComponentException("Attribute '{$attributeCode}' does not exist.");
+ }
+
+ $this->eavSetup->addAttributeToGroup(
+ Product::ENTITY,
+ $attributeSetEntity->getId(),
+ $group['name'],
+ $attributeCode
+ );
+ }
+ }
+ }
+
+ /**
+ * @param $attributeSetName
+ * @return string
+ */
+ protected function getAttributeSetId($attributeSetName)
+ {
+ $attributeSetData = $this->eavSetup->getAttributeSet(Product::ENTITY, $attributeSetName);
+ if (array_key_exists('attribute_set_id', $attributeSetData)) {
+ return $attributeSetData['attribute_set_id'];
+ }
+
+ throw new ComponentException('Could not find attribute set name.');
+ }
+}
diff --git a/Model/Component/Attributes.php b/Model/Component/Attributes.php
new file mode 100644
index 0000000..17fb261
--- /dev/null
+++ b/Model/Component/Attributes.php
@@ -0,0 +1,192 @@
+eavSetup = $eavSetup;
+ $this->productAttributeRepository = $repository;
+ }
+
+ /**
+ * @param array $attributeConfigurationData
+ */
+ protected function processData($attributeConfigurationData = null)
+ {
+ try {
+ foreach ($attributeConfigurationData['attributes'] as $attributeCode => $attributeConfiguration) {
+ $this->processAttribute($attributeCode, $attributeConfiguration);
+ }
+ } catch (ComponentException $e) {
+ $this->log->logError($e->getMessage());
+ }
+ }
+
+ /**
+ * @param $attributeCode
+ * @param $attributeConfig
+ */
+ protected function processAttribute($attributeCode, array $attributeConfig)
+ {
+ $updateAttribute = true;
+ $attributeExists = false;
+ $attributeArray = $this->eavSetup->getAttribute(Product::ENTITY, $attributeCode);
+ if ($attributeArray && $attributeArray['attribute_id']) {
+ $attributeExists = true;
+ $this->log->logComment(sprintf('Attribute %s exists. Checking for updates.', $attributeCode));
+ $updateAttribute = $this->checkForAttributeUpdates($attributeCode, $attributeArray, $attributeConfig);
+
+ if (isset($attributeConfig['option'])) {
+ $newAttributeOptions = $this->manageAttributeOptions($attributeCode, $attributeConfig['option']);
+ $attributeConfig['option']['values'] = $newAttributeOptions;
+ }
+ }
+
+ if ($updateAttribute) {
+
+ $attributeConfig['user_defined'] = 1;
+
+ if (isset($attributeConfig['product_types'])) {
+ $attributeConfig['apply_to'] = implode(',', $attributeConfig['product_types']);
+ }
+
+ $this->eavSetup->addAttribute(
+ Product::ENTITY,
+ $attributeCode,
+ $attributeConfig
+ );
+
+ if ($attributeExists) {
+ $this->log->logInfo(sprintf('Attribute %s updated.', $attributeCode));
+ return;
+ }
+
+ $this->log->logInfo(sprintf('Attribute %s created.', $attributeCode));
+ }
+ }
+
+ protected function checkForAttributeUpdates($attributeCode, $attributeArray, $attributeConfig)
+ {
+ $requiresUpdate = false;
+ $nest = 1;
+ foreach ($attributeConfig as $name => $value) {
+
+ if ($name == "product_types") {
+ $value = implode(',', $value);
+ }
+
+ $name = $this->mapAttributeConfig($name);
+
+ if ($name == 'option') {
+ continue;
+ }
+
+ if (!isset($attributeArray[$name])) {
+ $this->log->logError(sprintf(
+ 'Attribute %s type %s does not exist or is not mapped',
+ $attributeCode,
+ $name
+ ), $nest);
+ continue;
+ }
+
+ if ($attributeArray[$name] != $value) {
+ $this->log->logInfo(sprintf(
+ 'Update required for %s as %s is "%s" but should be "%s"',
+ $attributeCode,
+ $name,
+ $attributeArray[$name],
+ $value
+ ), $nest);
+
+ $requiresUpdate = true;
+
+ continue;
+ }
+
+ $this->log->logComment(sprintf(
+ 'No Update required for %s as %s is still "%s"',
+ $attributeCode,
+ $name,
+ $value
+ ), $nest);
+ }
+
+ return $requiresUpdate;
+ }
+
+ protected function mapAttributeConfig($name)
+ {
+ if ($name == 'label') {
+ $name = 'frontend_label';
+ }
+
+ if ($name == 'type') {
+ $name = 'backend_type';
+ }
+
+ if ($name == 'input') {
+ $name = 'frontend_input';
+ }
+
+ if ($name == 'product_types') {
+ $name = 'apply_to';
+ }
+ return $name;
+ }
+
+ private function manageAttributeOptions($attributeCode, $option)
+ {
+ $attributeOptions = $this->productAttributeRepository->get($attributeCode)->getOptions();
+
+ // Loop through existing attributes options
+ $existingAttributeOptions = array();
+ foreach ($attributeOptions as $attributeOption) {
+ $value = $attributeOption->getLabel();
+ $existingAttributeOptions[] = $value;
+ }
+
+ $optionsToAdd = array_diff($option['values'], $existingAttributeOptions);
+ //$optionsToRemove = array_diff($existingAttributeOptions, $option['values']);
+
+ return $optionsToAdd;
+ }
+}
diff --git a/README.md b/README.md
index 060e30c..43c511b 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ Do also include sample files with your component that works
| System Configuration | :white_check_mark: | :grey_exclamation: | :white_check_mark: |
| Categories | :white_check_mark: | :grey_exclamation: | :white_check_mark: |
| Products | :white_check_mark: | :grey_exclamation: | :white_check_mark: |
+| Attributes | :white_check_mark: | :grey_exclamation: | :white_check_mark: |
| Blocks | :white_check_mark: | :grey_exclamation: | :white_check_mark: |
| Admin Roles | :white_check_mark: | :grey_exclamation: | :white_check_mark: |
| Admin Users | :white_check_mark: | :grey_exclamation: | :white_check_mark: |
@@ -56,7 +57,6 @@ Do also include sample files with your component that works
| Tax Rules | :white_check_mark: | :grey_exclamation: | :white_check_mark: |
| Tax Rates | :x: | :x: | :x: |
| Attribute Sets | :x: | :x: | :x: |
-| Attributes | :x: | :x: | :x: |
| Customers | :x: | :x: | :x: |
| Related Products | :x: | :x: | :x: |
| SQL | :x: | :x: | :x: |
diff --git a/Samples/Components/Attributes/attribute_sets.yaml b/Samples/Components/Attributes/attribute_sets.yaml
new file mode 100644
index 0000000..3cd7b13
--- /dev/null
+++ b/Samples/Components/Attributes/attribute_sets.yaml
@@ -0,0 +1,39 @@
+attribute_sets:
+-
+ name: Shirts
+ inherit: Default
+ groups:
+ -
+ name: General
+ attributes:
+ - colour
+ - color
+ -
+ name: Prices
+ attributes:
+ - rrp
+ - test_attr
+-
+ name: Example Attribute Set 2
+ inherit: Default
+ groups:
+ -
+ name: Prices
+ attributes:
+ - rrp
+-
+ name: Matthew Attribute Set
+ inherit: Default
+ groups:
+ -
+ name: Prices
+ attributes:
+ - rrp
+-
+ name: Matthew Attribute Set 2
+ groups:
+ -
+ name: Prices
+ attributes:
+ - rrp
+
diff --git a/Samples/Components/Attributes/attributes.yaml b/Samples/Components/Attributes/attributes.yaml
new file mode 100644
index 0000000..17196e2
--- /dev/null
+++ b/Samples/Components/Attributes/attributes.yaml
@@ -0,0 +1,36 @@
+attributes:
+ test_attr:
+ is_global: 1
+ label: Test Attribute
+ type: text
+ input: select
+ is_visible_on_front: 1
+ is_filterable: 1
+ is_searchable: 1
+ is_visible_in_advanced_search: 0
+ product_types:
+ - simple
+ option:
+ values:
+ - Red
+ - Green
+ - Yellow
+ - Blue
+ - Purple
+ - Pink
+ - Orange
+ - Brown
+ rrp:
+ frontend_label: Recommended Retail Price
+ frontend_input: price
+ used_in_product_listing: 1
+ product_types:
+ - bundle
+ - simple
+ colour:
+ frontend_label: Colour
+ frontend_input: text
+ used_in_product_listing: 1
+ product_types:
+ - bundle
+ - simple
diff --git a/Samples/master.yaml b/Samples/master.yaml
index e6495fa..c690a77 100644
--- a/Samples/master.yaml
+++ b/Samples/master.yaml
@@ -106,4 +106,14 @@ media:
enabled: 1
method: code
sources:
- - ../configurator/Media/media.yaml
\ No newline at end of file
+ - ../configurator/Media/media.yaml
+attributes:
+ enabled: 1
+ method: code
+ sources:
+ - ../configurator/Attributes/attributes.yaml
+attribute_sets:
+ enabled: 1
+ method: code
+ sources:
+ - ../configurator/Attributes/attribute_sets.yaml
diff --git a/Test/Unit/Component/AttributeSetsTest.php b/Test/Unit/Component/AttributeSetsTest.php
new file mode 100644
index 0000000..5c4af17
--- /dev/null
+++ b/Test/Unit/Component/AttributeSetsTest.php
@@ -0,0 +1,31 @@
+getMock(EavSetup::class, [], [], '', false);
+ $attributeSetsRepositoryInterface = $this->getMock(AttributeSetRepositoryInterface::class);
+
+ $this->component = new AttributeSets(
+ $this->logInterface,
+ $this->objectManager,
+ $eavSetup,
+ $attributeSetsRepositoryInterface
+ );
+
+ $this->className = AttributeSets::class;
+ }
+}
diff --git a/Test/Unit/Component/AttributesTest.php b/Test/Unit/Component/AttributesTest.php
new file mode 100644
index 0000000..6670625
--- /dev/null
+++ b/Test/Unit/Component/AttributesTest.php
@@ -0,0 +1,19 @@
+getMock(EavSetup::class, [], [], '', false);
+ $attributeRepository = $this->getMock(ProductAttributeRepository::class, [], [], '', false);
+ $this->component = new Attributes($this->logInterface, $this->objectManager, $eavSetup, $attributeRepository);
+ $this->className = Attributes::class;
+ }
+}
diff --git a/composer.json b/composer.json
index c4f1f00..4a7bb36 100644
--- a/composer.json
+++ b/composer.json
@@ -21,7 +21,7 @@
"satooshi/php-coveralls": "^1.0",
"phpunit/phpunit": "4.1.0"
},
- "version": "0.11.0-dev",
+ "version": "0.12.0-dev",
"autoload": {
"files": [ "registration.php" ],
"psr-4": {
diff --git a/etc/configurator.xml b/etc/configurator.xml
index 1037ae0..faa013b 100644
--- a/etc/configurator.xml
+++ b/etc/configurator.xml
@@ -14,4 +14,6 @@
+
+
\ No newline at end of file