From 679d6687a5c55af008f83b97c5bfa2ca8314caa9 Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Mon, 3 Jun 2024 10:49:12 +0100 Subject: [PATCH 1/4] support for asset's native alt field --- src/base/Field.php | 45 ++++++++++++++++--- src/fields/Assets.php | 5 +++ .../_includes/elements/assets/map.html | 28 ++++++++---- src/templates/_includes/fields/_base.html | 21 ++++++--- src/web/twig/variables/FeedMeVariable.php | 5 ++- 5 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/base/Field.php b/src/base/Field.php index 65228ce7..344d0cdb 100644 --- a/src/base/Field.php +++ b/src/base/Field.php @@ -143,16 +143,47 @@ public function fetchValue(): mixed // ========================================================================= /** - * @param $elementIds - * @param null $nodeKey + * Save native field data on the elements. + * + * @param array $elementIds + * @param string|null $nodeKey + * @return void + * @throws Throwable + * @throws ElementNotFoundException + * @throws Exception + */ + protected function populateNativeFields(array $elementIds, string $nodeKey = null): void + { + $this->populateFields($elementIds, $nodeKey, 'native'); + } + + /** + * @param array $elementIds + * @param string|null $nodeKey + * @return void * @throws Throwable * @throws ElementNotFoundException * @throws Exception */ - protected function populateElementFields($elementIds, $nodeKey = null): void + protected function populateElementFields(array $elementIds, string $nodeKey = null): void + { + $this->populateFields($elementIds, $nodeKey); + } + + /** + * @param array $elementIds Array of elementIds to populate. + * @param string|null $nodeKey + * @param string $fieldType Type of field to populate, can be `custom` or `native`. + * @return void + * @throws Throwable + * @throws ElementNotFoundException + * @throws Exception + */ + private function populateFields(array $elementIds, string $nodeKey = null, string $fieldType = 'custom'): void { $elementsService = Craft::$app->getElements(); - $fields = Hash::get($this->fieldInfo, 'fields'); + $fieldTypeKey = $fieldType === 'custom' ? 'fields' : 'nativeFields'; + $fields = Hash::get($this->fieldInfo, $fieldTypeKey); $fieldData = []; @@ -180,7 +211,11 @@ protected function populateElementFields($elementIds, $nodeKey = null): void foreach ($fieldData as $elementId => $fieldContent) { $element = $elementsService->getElementById($elementId, null, Hash::get($this->feed, 'siteId')); - $element->setFieldValues($fieldContent); + if ($fieldType === 'native') { + $element->setAttributes($fieldContent); + } else { + $element->setFieldValues($fieldContent); + } Plugin::debug([ $this->fieldHandle => [ diff --git a/src/fields/Assets.php b/src/fields/Assets.php index d60d7bf6..cfb5334a 100644 --- a/src/fields/Assets.php +++ b/src/fields/Assets.php @@ -87,6 +87,7 @@ public function parseField(): mixed $upload = Hash::get($this->fieldInfo, 'options.upload'); $conflict = Hash::get($this->fieldInfo, 'options.conflict'); $fields = Hash::get($this->fieldInfo, 'fields'); + $nativeFields = Hash::get($this->fieldInfo, 'nativeFields'); $node = Hash::get($this->fieldInfo, 'node'); $nodeKey = null; @@ -252,6 +253,10 @@ public function parseField(): mixed $this->populateElementFields($foundElements, $nodeKey); } + if ($nativeFields) { + $this->populateNativeFields($foundElements, $nodeKey); + } + $foundElements = array_unique($foundElements); // Protect against sending an empty array - removing any existing elements diff --git a/src/templates/_includes/elements/assets/map.html b/src/templates/_includes/elements/assets/map.html index 50b9bfb7..5e89bd1c 100644 --- a/src/templates/_includes/elements/assets/map.html +++ b/src/templates/_includes/elements/assets/map.html @@ -86,14 +86,26 @@

{{ tab.name }} Fields

{{ 'Default Value'|t('feed-me') }} - {% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField')) %} - {% set field = layoutField.getField() %} - {% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %} - {% set template = fieldClass.getMappingTemplate() %} - - {% set variables = { name: field.name, handle: field.handle, feed: feed, feedData: feedData, field: field, fieldClass: fieldClass } %} - - {% include template ignore missing with variables only %} + {% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField') or e is instance of('craft\\fieldlayoutelements\\assets\\AltField')) %} + {% if layoutField is instance of('craft\\fieldlayoutelements\\assets\\AltField') %} + {% set variables = { + name: 'Alternative Text'|t('feed-me'), + handle: 'alt', + feed: feed, + feedData: feedData, + attribute: true, + } %} + + {% include 'feed-me/_includes/fields/default' ignore missing with variables only %} + {% else %} + {% set field = layoutField.getField() %} + {% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %} + {% set template = fieldClass.getMappingTemplate() %} + + {% set variables = { name: field.name, handle: field.handle, feed: feed, feedData: feedData, field: field, fieldClass: fieldClass } %} + + {% include template ignore missing with variables only %} + {% endif %} {% endfor %} diff --git a/src/templates/_includes/fields/_base.html b/src/templates/_includes/fields/_base.html index 2c050ee1..9e378023 100644 --- a/src/templates/_includes/fields/_base.html +++ b/src/templates/_includes/fields/_base.html @@ -199,18 +199,25 @@ {% for elementField in elementFields %} {% set fieldClass = craft.feedme.fields.getRegisteredField(className(elementField)) %} {% set template = fieldClass.getMappingTemplate() %} - - {% if parentPath is defined %} - {% set path = parentPath|merge([ 'fields', elementField.handle ]) %} + {% set attribute = elementField['attribute'] ?? null %} + {% set elementFieldHandle = elementField['handle'] ?? attribute ?? '' %} + + {% if parentPath is defined and attribute %} + {% set path = parentPath|merge([ 'nativeFields', elementFieldHandle ]) %} + {% elseif parentPath is defined %} + {% set path = parentPath|merge([ 'fields', elementFieldHandle ]) %} + {% elseif attribute %} + {% set path = [ handle, 'nativeFields', attribute ] %} {% else %} - {% set path = [ handle, 'fields', elementField.handle ] %} + {% set path = [ handle, 'fields', elementFieldHandle ] %} {% endif %} {# Be smart about what we include to child field templates #} {% include template ignore missing with { - name: elementField.name, - handle: elementField.handle, - instructionHandle: elementField.handle, + name: elementField['name'] ?? elementField['label'] ?? '', + attribute: elementField['attribute'] ?? null, + handle: elementFieldHandle, + instructionHandle: elementFieldHandle, isSubElementField: true, path: path, diff --git a/src/web/twig/variables/FeedMeVariable.php b/src/web/twig/variables/FeedMeVariable.php index a77436d2..3f6d59a9 100644 --- a/src/web/twig/variables/FeedMeVariable.php +++ b/src/web/twig/variables/FeedMeVariable.php @@ -5,6 +5,7 @@ use Craft; use craft\elements\User as UserElement; use craft\feedme\Plugin; +use craft\fieldlayoutelements\assets\AltField; use craft\fields\Checkboxes; use craft\fields\Color; use craft\fields\Date; @@ -16,6 +17,7 @@ use craft\fields\PlainText; use craft\fields\RadioButtons; use craft\fields\Url; +use craft\helpers\ArrayHelper; use craft\helpers\DateTimeHelper; use craft\helpers\Html; use craft\helpers\UrlHelper; @@ -240,7 +242,7 @@ public function getElementLayoutByField($type, $field): ?array } if (($fieldLayout = Craft::$app->getFields()->getLayoutById($source->fieldLayoutId)) !== null) { - return $fieldLayout->getCustomFields(); + return ArrayHelper::merge($fieldLayout->getCustomFields(), $fieldLayout->getAvailableNativeFields()); } return null; @@ -352,6 +354,7 @@ public function supportedSubField($class): bool RadioButtons::class, 'craft\ckeditor\Field', 'craft\redactor\Field', + AltField::class, ]; return in_array($class, $supportedSubFields, true); From 9f4a672bd024d14818087c9e805c17f902f2e73e Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Mon, 10 Jun 2024 15:42:33 +0100 Subject: [PATCH 2/4] use layout field's name and handle for alt text --- src/templates/_includes/elements/assets/map.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/templates/_includes/elements/assets/map.html b/src/templates/_includes/elements/assets/map.html index 5e89bd1c..52d645e0 100644 --- a/src/templates/_includes/elements/assets/map.html +++ b/src/templates/_includes/elements/assets/map.html @@ -89,8 +89,8 @@

{{ tab.name }} Fields

{% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField') or e is instance of('craft\\fieldlayoutelements\\assets\\AltField')) %} {% if layoutField is instance of('craft\\fieldlayoutelements\\assets\\AltField') %} {% set variables = { - name: 'Alternative Text'|t('feed-me'), - handle: 'alt', + name: layoutField.getAttributeLabel(layoutField.attribute), + handle: layoutField.attribute, feed: feed, feedData: feedData, attribute: true, From 504c18c3a713548a9e587bf9c5a86b425f4a8273 Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Mon, 10 Jun 2024 15:53:50 +0100 Subject: [PATCH 3/4] php stan --- src/base/Field.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/base/Field.php b/src/base/Field.php index 344d0cdb..f9a993ea 100644 --- a/src/base/Field.php +++ b/src/base/Field.php @@ -198,7 +198,6 @@ private function populateFields(array $elementIds, string $nodeKey = null, strin // Arrayed content doesn't provide defaults because it's unable to determine how many items it _should_ return // This also checks if there was any data that corresponds on the same array index/level as our element - /** @phpstan-ignore-next-line */ $value = Hash::get($fieldValue, $nodeKey ?? $key, $default); if ($value) { From e385f6d8516217503b211ae743f0e4e2ca186117 Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Fri, 21 Jun 2024 13:29:21 +0100 Subject: [PATCH 4/4] native addresses WIP --- src/events/RegisterFeedMeFieldsEvent.php | 5 + src/fieldlayoutelements/assets/Alt.php | 57 +++++ src/fieldlayoutelements/users/Addresses.php | 202 ++++++++++++++++++ src/fields/Assets.php | 2 + src/services/Fields.php | 103 +++++++++ src/services/Process.php | 43 +++- .../_includes/elements/assets/map.html | 23 +- .../_includes/elements/user/map.html | 31 ++- .../addresses/address.html | 54 +++++ .../addresses/lat-long.html | 47 ++++ .../fieldlayoutelements/assets/alt.twig | 21 ++ .../fieldlayoutelements/default.twig | 31 +++ .../fieldlayoutelements/users/addresses.html | 78 +++++++ src/templates/_includes/fields/_base.html | 42 ++-- 14 files changed, 711 insertions(+), 28 deletions(-) create mode 100644 src/fieldlayoutelements/assets/Alt.php create mode 100644 src/fieldlayoutelements/users/Addresses.php create mode 100644 src/templates/_includes/fieldlayoutelements/addresses/address.html create mode 100644 src/templates/_includes/fieldlayoutelements/addresses/lat-long.html create mode 100644 src/templates/_includes/fieldlayoutelements/assets/alt.twig create mode 100644 src/templates/_includes/fieldlayoutelements/default.twig create mode 100644 src/templates/_includes/fieldlayoutelements/users/addresses.html diff --git a/src/events/RegisterFeedMeFieldsEvent.php b/src/events/RegisterFeedMeFieldsEvent.php index 6638c0c5..961b6a54 100644 --- a/src/events/RegisterFeedMeFieldsEvent.php +++ b/src/events/RegisterFeedMeFieldsEvent.php @@ -13,4 +13,9 @@ class RegisterFeedMeFieldsEvent extends Event * @var array */ public array $fields = []; + + /** + * @var array + */ + public array $nativeFields = []; } diff --git a/src/fieldlayoutelements/assets/Alt.php b/src/fieldlayoutelements/assets/Alt.php new file mode 100644 index 00000000..831bb19b --- /dev/null +++ b/src/fieldlayoutelements/assets/Alt.php @@ -0,0 +1,57 @@ +parseField() + $value = $this->fetchValue(); + + if ($value === null) { + return null; + } + + return $value; + } +} diff --git a/src/fieldlayoutelements/users/Addresses.php b/src/fieldlayoutelements/users/Addresses.php new file mode 100644 index 00000000..4b1458ed --- /dev/null +++ b/src/fieldlayoutelements/users/Addresses.php @@ -0,0 +1,202 @@ +fieldInfo, 'fields'); + $nativeFields = Hash::get($this->fieldInfo, 'nativeFields'); + + // figure out how many addresses we have in the dataset + $fieldGroups = array_filter( + ['nativeFields' => $nativeFields, 'fields' => $fields], + fn($group) => !empty($group) + ); + $noAddresses = $this->getNumberOfAddresses($fieldGroups); + + for ($i = 0; $i < $noAddresses; $i++) { + // we have to find/create Address element here + // todo - should we allow finding an existing address? if so, what should we map by? id and custom fields? + $element = $this->_createElement($nativeFields, $i); + + // if we were able to save the address, and we have fields to process, go ahead and do that + if ($element && $fields) { + $this->populateElementFields([$element->id], $i); + } + + // returning just the address id for compareElementContent check + $fieldValue[] = $element->id; + } + + return $fieldValue; + } + + private function handleNativeFields(ElementInterface $element, array $nativeFields, int $nodeKey): void + { + $attributeValues = []; + foreach ($nativeFields as $fieldHandle => $fieldInfo) { + $default = Hash::get($fieldInfo, 'default'); + //$fieldValue = DataHelper::fetchArrayValue($this->feedData, $fieldInfo); + $fieldValue = DataHelper::fetchValue($this->feedData, $fieldInfo, $this->feed); + + // Find the class to deal with the attribute + $name = 'parse' . ucwords($fieldHandle); + + // Set a default handler for non-specific attribute classes + if (!method_exists($this, $name)) { + $value = Hash::get($fieldValue, $nodeKey, $default); + } else { + $value = $this->$name($element, $fieldInfo, $nodeKey); + } + + if (!empty($value)) { + $attributeValues[$fieldHandle] = $value; + } + } + + if (!empty($attributeValues)) { + $element->setAttributes($attributeValues, false); + } + } + + private function parseAddress($element, $fieldInfo, $nodeKey): void + { + $nativeFields = Hash::get($fieldInfo, 'nativeFields'); + $this->handleNativeFields($element, $nativeFields, $nodeKey); + } + + private function parseLatLong($element, $fieldInfo, $nodeKey): void + { + $nativeFields = Hash::get($fieldInfo, 'nativeFields'); + $this->handleNativeFields($element, $nativeFields, $nodeKey); + } + + private function getNumberOfAddresses(array $fields): int + { + $noAddresses = 0; + + foreach ($fields as $fieldTypeGroup) { + foreach ($fieldTypeGroup as $fieldInfo) { + $node = Hash::get($fieldInfo, 'node'); + if ($node) { + $nodeSegments = explode('/', $node); + $regex = str_replace('//', '/', implode('\/\d?\/', $nodeSegments)); + $matches = preg_grep('/' . $regex . '/', array_keys($this->feedData)); + if (count($matches) > $noAddresses) { + $noAddresses = count($matches); + } + } + } + } + + return $noAddresses; + } + + // Private Methods + // ========================================================================= + + /** + * @param array $nativeFields + * @param int $nodeIndex + * @return ElementInterface|null + */ + private function _createElement(array $nativeFields, int $nodeIndex): ?ElementInterface + { + $element = new AddressElement(); + $element->setScenario(Element::SCENARIO_ESSENTIALS); + $element->setOwner($this->element); + + // native fields have to go first! + if ($nativeFields) { + $this->handleNativeFields($element, $nativeFields, $nodeIndex); + } + + if (!Craft::$app->getElements()->saveElement($element)) { + Plugin::error('Address error: Could not create - `{e}`.', ['e' => Json::encode($element->getErrors())]); + + return null; + } + + Plugin::info('Address `#{id}` added.', ['id' => $element->id]); + + return $element; + } + + /** + * Attempt to find User based on search criteria. Return array of found IDs. + * + * @param $criteria + * @return array|int[] + */ + private function _findAddresses($criteria): array + { + $query = AddressElement::find(); + Craft::configure($query, $criteria); + + Plugin::info('Search for existing address with query `{i}`', ['i' => json_encode($criteria)]); + + $ids = $query->ids(); + + Plugin::info('Found `{i}` existing addresses: `{j}`', ['i' => count($ids), 'j' => json_encode($ids)]); + + return $ids; + } +} diff --git a/src/fields/Assets.php b/src/fields/Assets.php index cfb5334a..06e240f1 100644 --- a/src/fields/Assets.php +++ b/src/fields/Assets.php @@ -253,6 +253,8 @@ public function parseField(): mixed $this->populateElementFields($foundElements, $nodeKey); } + // this is used by the sub-fields of the assets field; not when importing into Asset element directly; + // when importing into Asset element directly, src/fieldlayoutelements/assets/Alt.php is used if ($nativeFields) { $this->populateNativeFields($foundElements, $nodeKey); } diff --git a/src/services/Fields.php b/src/services/Fields.php index 8615fa42..01cba478 100644 --- a/src/services/Fields.php +++ b/src/services/Fields.php @@ -10,6 +10,8 @@ use craft\feedme\base\FieldInterface; use craft\feedme\events\FieldEvent; use craft\feedme\events\RegisterFeedMeFieldsEvent; +use craft\feedme\fieldlayoutelements\assets\Alt; +use craft\feedme\fieldlayoutelements\users\Addresses; use craft\feedme\fields\Assets; use craft\feedme\fields\CalendarEvents; use craft\feedme\fields\Categories; @@ -54,6 +56,8 @@ class Fields extends Component public const EVENT_REGISTER_FEED_ME_FIELDS = 'registerFeedMeFields'; public const EVENT_BEFORE_PARSE_FIELD = 'onBeforeParseField'; public const EVENT_AFTER_PARSE_FIELD = 'onAfterParseField'; + public const EVENT_BEFORE_PARSE_NATIVE_FIELD = 'onBeforeParseNativeField'; + public const EVENT_AFTER_PARSE_NATIVE_FIELD = 'onAfterParseNativeField'; // Properties @@ -64,6 +68,11 @@ class Fields extends Component */ private array $_fields = []; + /** + * @var array + */ + private array $_nativeFields = []; + // Public Methods // ========================================================================= @@ -86,6 +95,19 @@ public function init(): void $this->_fields[$handle] = $field; } + + foreach ($this->getRegisteredNativeFields() as $fieldClass) { + $field = $this->createField($fieldClass); + + // Does this native field exist in Craft right now? + if (!class_exists($field::$class)) { + continue; + } + + $handle = $field::$class; + + $this->_nativeFields[$handle] = $field; + } } /** @@ -98,6 +120,16 @@ public function getRegisteredField($handle): mixed return $this->_fields[$handle] ?? $this->createField(DefaultField::class); } + /** + * @param $handle + * @return ComponentInterface|MissingDataType|mixed + * @throws InvalidConfigException + */ + public function getRegisteredNativeField($handle): mixed + { + return $this->_nativeFields[$handle] ?? $this->createField(DefaultField::class); + } + /** * @return array */ @@ -160,6 +192,28 @@ public function getRegisteredFields(): array return $event->fields; } + + /** + * @return array + */ + public function getRegisteredNativeFields(): array + { + if (count($this->_nativeFields)) { + return $this->_nativeFields; + } + + $event = new RegisterFeedMeFieldsEvent([ + 'nativeFields' => [ + Addresses::class, + Alt::class + ], + ]); + + $this->trigger(self::EVENT_REGISTER_FEED_ME_FIELDS, $event); + + return $event->nativeFields; + } + /** * @param $config * @return FieldInterface @@ -248,4 +302,53 @@ public function parseField($feed, $element, $feedData, $fieldHandle, $fieldInfo) $this->trigger(self::EVENT_AFTER_PARSE_FIELD, $event); return $event->parsedValue; } + + /** + * @param $feed + * @param $element + * @param $feedData + * @param $fieldHandle + * @param $fieldInfo + * @return mixed + */ + public function parseNativeField($feed, $element, $feedData, $fieldHandle, $fieldInfo): mixed + { + if ($this->hasEventHandlers(self::EVENT_BEFORE_PARSE_NATIVE_FIELD)) { + $this->trigger(self::EVENT_BEFORE_PARSE_NATIVE_FIELD, new FieldEvent([ + 'feedData' => $feedData, + 'fieldHandle' => $fieldHandle, + 'fieldInfo' => $fieldInfo, + 'element' => $element, + 'feed' => $feed, + ])); + } + + $fieldClassHandle = Hash::get($fieldInfo, 'nativeField'); + + $fieldLayout = Craft::$app->getFields()->getLayoutByType($element::class); + $field = $fieldLayout->getField($fieldHandle); + + // Find the class to deal with the attribute + $class = $this->getRegisteredNativeField($fieldClassHandle); + $class->feedData = $feedData; + $class->fieldHandle = $fieldHandle; + $class->fieldInfo = $fieldInfo; + $class->field = $field; + $class->element = $element; + $class->feed = $feed; + + // Get that sweet data + $parsedValue = $class->parseField(); + + $event = new FieldEvent([ + 'feedData' => $feedData, + 'fieldHandle' => $fieldHandle, + 'fieldInfo' => $fieldInfo, + 'element' => $element, + 'feed' => $feed, + 'parsedValue' => $parsedValue, + ]); + $this->trigger(self::EVENT_AFTER_PARSE_NATIVE_FIELD, $event); + return $event->parsedValue; + } } diff --git a/src/services/Process.php b/src/services/Process.php index 57e795c9..83bd5613 100644 --- a/src/services/Process.php +++ b/src/services/Process.php @@ -179,6 +179,7 @@ public function processFeed($step, $feed, &$processedElementIds, $feedData = nul { $attributeData = []; $fieldData = []; + $nativeFieldData = []; // We can opt-out of updating certain elements if a field is switched on $skipUpdateFieldHandle = Plugin::$plugin->service->getConfig('skipUpdateFieldHandle', $feed['id']); @@ -399,6 +400,23 @@ public function processFeed($step, $feed, &$processedElementIds, $feedData = nul // Set the attributes for the element $element->setAttributes($attributeData, false); + foreach ($feed['fieldMapping'] as $fieldHandle => $fieldInfo) { + if (Hash::get($fieldInfo, 'nativeField')) { + $fieldValue = Plugin::$plugin->fields->parseNativeField($feed, $element, $feedData, $fieldHandle, $fieldInfo); + + if ($fieldValue !== null) { + if ($feed['setEmptyValues'] || + (!empty($fieldValue) || is_numeric($fieldValue) || is_bool($fieldValue)) + ) { + // we set this for $fieldValue['hasElements'] too so that the logs are accurate when it comes to comparing values + $nativeFieldData[$fieldHandle] = $fieldValue; + } + } + } + } + + $element->setAttributes($nativeFieldData, false); + // Then, do the same for custom fields. Again, this should be done after populating the element attributes foreach ($feed['fieldMapping'] as $fieldHandle => $fieldInfo) { if (Hash::get($fieldInfo, 'field')) { @@ -426,17 +444,22 @@ public function processFeed($step, $feed, &$processedElementIds, $feedData = nul $attributeData[$key] = DataHelper::parseFieldDataForElement($value, $element); } + foreach ($nativeFieldData as $key => $value) { + $nativeFieldData[$key] = DataHelper::parseFieldDataForElement($value, $element); + } + foreach ($fieldData as $key => $value) { $fieldData[$key] = DataHelper::parseFieldDataForElement($value, $element); } // Set the attributes and fields again $element->setAttributes($attributeData, false); + $element->setAttributes($nativeFieldData, false); $element->setFieldValues($fieldData); } // We need to keep these separate to apply to the element but required when matching against existing elements - $contentData += $attributeData + $fieldData; + $contentData += $attributeData + $nativeFieldData + $fieldData; // // It's time to actually save the element! @@ -724,7 +747,23 @@ private function _filterUnmappedFields($fields): array if ($node) { $fields = Hash::remove($fields, $infoPath . '.fields'); } else { - $fields = Hash::remove($fields, $infoPath); + if (Hash::get($fields, $infoPath . '.nativeFields')) { + $fields = Hash::remove($fields, $key); + } else { + $fields = Hash::remove($fields, $infoPath); + } + } + } + + if ($lastIndex === 'nativeFields' && empty($value)) { + if ($node) { + $fields = Hash::remove($fields, $infoPath . '.nativeFields'); + } else { + if (Hash::get($fields, $infoPath . '.fields')) { + $fields = Hash::remove($fields, $key); + } else { + $fields = Hash::remove($fields, $infoPath); + } } } diff --git a/src/templates/_includes/elements/assets/map.html b/src/templates/_includes/elements/assets/map.html index 52d645e0..e41f2d46 100644 --- a/src/templates/_includes/elements/assets/map.html +++ b/src/templates/_includes/elements/assets/map.html @@ -88,24 +88,33 @@

{{ tab.name }} Fields

{% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField') or e is instance of('craft\\fieldlayoutelements\\assets\\AltField')) %} {% if layoutField is instance of('craft\\fieldlayoutelements\\assets\\AltField') %} + {% set fieldClass = craft.feedme.fields.getRegisteredNativeField(className(layoutField)) %} + {% set variables = { name: layoutField.getAttributeLabel(layoutField.attribute), handle: layoutField.attribute, feed: feed, feedData: feedData, - attribute: true, + nativeField: layoutField, + fieldClass: fieldClass, } %} - - {% include 'feed-me/_includes/fields/default' ignore missing with variables only %} {% else %} {% set field = layoutField.getField() %} {% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %} - {% set template = fieldClass.getMappingTemplate() %} - - {% set variables = { name: field.name, handle: field.handle, feed: feed, feedData: feedData, field: field, fieldClass: fieldClass } %} - {% include template ignore missing with variables only %} + {% set variables = { + name: field.name, + handle: field.handle, + feed: feed, + feedData: feedData, + field: field, + fieldClass: fieldClass + } %} {% endif %} + + {% set template = fieldClass.getMappingTemplate() %} + + {% include template ignore missing with variables only %} {% endfor %} diff --git a/src/templates/_includes/elements/user/map.html b/src/templates/_includes/elements/user/map.html index 0f1366a0..3aa956e7 100644 --- a/src/templates/_includes/elements/user/map.html +++ b/src/templates/_includes/elements/user/map.html @@ -142,12 +142,33 @@

{{ tab.name }} Fields

{{ 'Default Value'|t('feed-me') }} - {% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField')) %} - {% set field = layoutField.getField() %} - {% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %} - {% set template = fieldClass.getMappingTemplate() %} + {% for layoutField in tab.getElements()|filter(e => e is instance of('craft\\fieldlayoutelements\\CustomField') or e is instance of('craft\\fieldlayoutelements\\users\\AddressesField')) %} + {% if layoutField is instance of('craft\\fieldlayoutelements\\users\\AddressesField') %} + {% set fieldClass = craft.feedme.fields.getRegisteredNativeField(className(layoutField)) %} + + {% set variables = { + name: layoutField.getAttributeLabel(layoutField.attribute), + handle: layoutField.attribute, + feed: feed, + feedData: feedData, + nativeField: layoutField, + fieldClass: fieldClass, + } %} + {% else %} + {% set field = layoutField.getField() %} + {% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %} + + {% set variables = { + name: field.name, + handle: field.handle, + feed: feed, + feedData: feedData, + field: field, + fieldClass: fieldClass + } %} + {% endif %} - {% set variables = { name: field.name, handle: field.handle, feed: feed, feedData: feedData, field: field, fieldClass: fieldClass } %} + {% set template = fieldClass.getMappingTemplate() %} {% include template ignore missing with variables only %} {% endfor %} diff --git a/src/templates/_includes/fieldlayoutelements/addresses/address.html b/src/templates/_includes/fieldlayoutelements/addresses/address.html new file mode 100644 index 00000000..d75edefa --- /dev/null +++ b/src/templates/_includes/fieldlayoutelements/addresses/address.html @@ -0,0 +1,54 @@ +{# ------------------------ #} +{# Available Variables #} +{# ------------------------ #} +{# Attributes: #} +{# type, name, handle, instructions, attribute, default, feed, feedData #} +{# ------------------------ #} +{# Fields: #} +{# name, handle, instructions, feed, feedData, field, fieldClass #} +{# ------------------------ #} + +{% import 'feed-me/_macros' as feedMeMacro %} +{% import '_includes/forms' as forms %} + +{# Special case when inside another complex field (Matrix) #} +{% if path is defined %} + {% set prefixPath = path %} +{% else %} + {% set prefixPath = [handle] %} +{% endif %} + +{% namespace 'fieldMapping[' ~ prefixPath|join('][') ~ ']' %} + +{% endnamespace %} + +{# we can't only get used address fields or get their adjusted labels like we do in the CP + because we don't know for which country will the each address in the dataset be +#} +{% set addressFields = { + 'administrativeArea': 'Administrative Area'|t('feed-me'), + 'locality': 'Locality'|t('feed-me'), + 'dependentLocality': 'Dependent Locality'|t('feed-me'), + 'postalCode': 'Postal Code'|t('feed-me'), + 'sortingCode': 'Sorting Code'|t('feed-me'), + 'addressLine1': 'Address Line 1'|t('feed-me'), + 'addressLine2': 'Address Line 2'|t('feed-me'), + 'organization': 'Organization'|t('feed-me'), + 'givenName': 'Given Name'|t('feed-me'), + 'additionalName': 'Additional Name'|t('feed-me'), + 'familyName': 'Family Name'|t('feed-me'), +} %} +{% set path = prefixPath|merge(['nativeFields', nativeField.attribute]) %} +{% for handle, label in addressFields %} + {% set path = prefixPath|merge(['nativeFields', handle]) %} + {% set variables = { + name: label, + handle: handle, + feed: feed, + feedData: feedData, + attribute: true, + path: path, + } %} + + {% include 'feed-me/_includes/fields/default' ignore missing with variables only %} +{% endfor %} \ No newline at end of file diff --git a/src/templates/_includes/fieldlayoutelements/addresses/lat-long.html b/src/templates/_includes/fieldlayoutelements/addresses/lat-long.html new file mode 100644 index 00000000..dd7ab5c0 --- /dev/null +++ b/src/templates/_includes/fieldlayoutelements/addresses/lat-long.html @@ -0,0 +1,47 @@ +{# ------------------------ #} +{# Available Variables #} +{# ------------------------ #} +{# Attributes: #} +{# type, name, handle, instructions, attribute, default, feed, feedData #} +{# ------------------------ #} +{# Fields: #} +{# name, handle, instructions, feed, feedData, field, fieldClass #} +{# ------------------------ #} + +{% import 'feed-me/_macros' as feedMeMacro %} +{% import '_includes/forms' as forms %} + +{# Special case when inside another complex field (Matrix) #} +{% if path is defined %} + {% set prefixPath = path %} +{% else %} + {% set prefixPath = [handle] %} +{% endif %} + +{% namespace 'fieldMapping[' ~ prefixPath|join('][') ~ ']' %} + +{% endnamespace %} + +{% set path = prefixPath|merge(['nativeFields', 'latitude']) %} +{% set variables = { + name: 'Latitude', + handle: 'latitude', + feed: feed, + feedData: feedData, + attribute: true, + path: path, +} %} + +{% include 'feed-me/_includes/fields/default' ignore missing with variables only %} + +{% set path = prefixPath|merge(['nativeFields', 'longitude']) %} +{% set variables = { + name: 'Longitude', + handle: 'longitude', + feed: feed, + feedData: feedData, + attribute: true, + path: path, +} %} + +{% include 'feed-me/_includes/fields/default' ignore missing with variables only %} \ No newline at end of file diff --git a/src/templates/_includes/fieldlayoutelements/assets/alt.twig b/src/templates/_includes/fieldlayoutelements/assets/alt.twig new file mode 100644 index 00000000..2c038165 --- /dev/null +++ b/src/templates/_includes/fieldlayoutelements/assets/alt.twig @@ -0,0 +1,21 @@ +{# ------------------------ #} +{# Available Variables #} +{# ------------------------ #} +{# Attributes: #} +{# type, name, handle, instructions, attribute, default, feed, feedData #} +{# ------------------------ #} +{# Fields: #} +{# name, handle, instructions, feed, feedData, field, fieldClass #} +{# ------------------------ #} + +{% import 'feed-me/_macros' as feedMeMacro %} +{% import '_includes/forms' as forms %} + +{% set default = default ?? { + type: 'text', +} %} + +{% extends 'feed-me/_includes/fields/_base' %} + +{% block extraSettings %} +{% endblock %} diff --git a/src/templates/_includes/fieldlayoutelements/default.twig b/src/templates/_includes/fieldlayoutelements/default.twig new file mode 100644 index 00000000..665f06b0 --- /dev/null +++ b/src/templates/_includes/fieldlayoutelements/default.twig @@ -0,0 +1,31 @@ +{# ------------------------ #} +{# Available Variables #} +{# ------------------------ #} +{# Attributes: #} +{# type, name, handle, instructions, attribute, default, feed, feedData #} +{# ------------------------ #} +{# Fields: #} +{# name, handle, instructions, feed, feedData, field, fieldClass #} +{# ------------------------ #} + +{% import 'feed-me/_macros' as feedMeMacro %} +{% import '_includes/forms' as forms %} + +{# Special case when inside another complex field (Matrix) #} +{% if parent is defined %} + {% set prefixPath = path %} +{% else %} + {% set prefixPath = [handle] %} +{% endif %} + +{% set variables = { + name: nativeField.getAttributeLabel(nativeField.attribute), + handle: nativeField.attribute, + feed: feed, + feedData: feedData, + nativeField: nativeField, + fieldClass: fieldClass, + path: path +} %} + +{% include 'feed-me/_includes/fields/default' ignore missing with variables only %} \ No newline at end of file diff --git a/src/templates/_includes/fieldlayoutelements/users/addresses.html b/src/templates/_includes/fieldlayoutelements/users/addresses.html new file mode 100644 index 00000000..328a87c6 --- /dev/null +++ b/src/templates/_includes/fieldlayoutelements/users/addresses.html @@ -0,0 +1,78 @@ +{# ------------------------ #} +{# Available Variables #} +{# ------------------------ #} +{# Attributes: #} +{# type, name, handle, instructions, attribute, default, feed, feedData #} +{# ------------------------ #} +{# Fields: #} +{# name, handle, instructions, feed, feedData, field, fieldClass #} +{# ------------------------ #} + +{% import 'feed-me/_macros' as feedMeMacro %} +{% import '_includes/forms' as forms %} + +{# Special case when inside another complex field (Matrix) #} +{% if parentPath is defined %} + {% set prefixPath = parentPath %} +{% else %} + {% set prefixPath = [handle] %} +{% endif %} +{#{% set classes = ['complex-field'] %}#} + + + +
+
+ +
+ +
+ {% namespace 'fieldMapping[' ~ prefixPath|join('][') ~ ']' %} + + {% endnamespace %} +
+
+ + + +{% set fieldLayout = craft.app.fields.getLayoutByType('craft\\elements\\Address') %} +{% set fields = [] %} +{#{% set prefixPath = prefixPath|merge(['item']) %}#} +{% if fieldLayout %} + {% for tab in fieldLayout.getTabs() %} + {% for layoutField in tab.getElements()|filter(e => e is instance of ('craft\\fieldlayoutelements\\BaseField')) %} + {% if layoutField is instance of ('craft\\fieldlayoutelements\\CustomField') %} + {% set field = layoutField.getField() %} + {% set fieldClass = craft.feedme.fields.getRegisteredField(className(field)) %} + {% set path = prefixPath|merge(['fields', field.handle]) %} + + {% set variables = { + name: field.name, + handle: field.handle, + feed: feed, + feedData: feedData, + field: field, + fieldClass: fieldClass, + path: path + } %} + {% else %} + {% set fieldClass = craft.feedme.fields.getRegisteredNativeField(className(layoutField)) %} + {% set path = prefixPath|merge(['nativeFields', layoutField.attribute]) %} + + {% set variables = { + name: layoutField.getAttributeLabel(layoutField.attribute), + handle: layoutField.attribute, + feed: feed, + feedData: feedData, + nativeField: layoutField, + fieldClass: fieldClass, + path: path + } %} + {% endif %} + + {% set template = fieldClass.getMappingTemplate() %} + + {% include template ignore missing with variables only %} + {% endfor %} + {% endfor %} +{% endif %} diff --git a/src/templates/_includes/fields/_base.html b/src/templates/_includes/fields/_base.html index 9e378023..189e850e 100644 --- a/src/templates/_includes/fields/_base.html +++ b/src/templates/_includes/fields/_base.html @@ -9,6 +9,7 @@ {% set required = required ?? null %} {% set attribute = attribute ?? null %} {% set field = field ?? null %} +{% set nativeField = nativeField ?? null %} {% set fieldClass = fieldClass ?? null %} {# Variables to help with fetching value/defaults and namespacing. Should provide an array that reflects the #} @@ -74,6 +75,10 @@ {% if field and fieldClass %} {% endif %} + + {% if nativeField and fieldClass %} + + {% endif %} {% endblock %} {% endnamespace %} @@ -197,25 +202,32 @@ {% block elementFields %} {% if field is defined and elementFields|length %} {% for elementField in elementFields %} - {% set fieldClass = craft.feedme.fields.getRegisteredField(className(elementField)) %} - {% set template = fieldClass.getMappingTemplate() %} - {% set attribute = elementField['attribute'] ?? null %} - {% set elementFieldHandle = elementField['handle'] ?? attribute ?? '' %} - - {% if parentPath is defined and attribute %} - {% set path = parentPath|merge([ 'nativeFields', elementFieldHandle ]) %} - {% elseif parentPath is defined %} - {% set path = parentPath|merge([ 'fields', elementFieldHandle ]) %} - {% elseif attribute %} - {% set path = [ handle, 'nativeFields', attribute ] %} + {% set elementFieldHandle = elementField['handle'] ?? elementField['attribute'] ?? '' %} + {% set nativeField = false %} + + {% if elementField is instance of('craft\\base\\Field') %} + {% set fieldClass = craft.feedme.fields.getRegisteredField(className(elementField)) %} + {% if parentPath is defined %} + {% set path = parentPath|merge([ 'fields', elementFieldHandle ]) %} + {% else %} + {% set path = [ handle, 'fields', elementFieldHandle ] %} + {% endif %} + {% set nativeField = false %} {% else %} - {% set path = [ handle, 'fields', elementFieldHandle ] %} + {% set fieldClass = craft.feedme.fields.getRegisteredNativeField(className(elementField)) %} + {% if parentPath is defined %} + {% set path = parentPath|merge([ 'nativeFields', elementFieldHandle ]) %} + {% else %} + {% set path = [ handle, 'nativeFields', elementFieldHandle ] %} + {% endif %} + {% set nativeField = true %} {% endif %} + {% set template = fieldClass.getMappingTemplate() %} + {# Be smart about what we include to child field templates #} {% include template ignore missing with { name: elementField['name'] ?? elementField['label'] ?? '', - attribute: elementField['attribute'] ?? null, handle: elementFieldHandle, instructionHandle: elementFieldHandle, isSubElementField: true, @@ -223,7 +235,9 @@ feed: feed, feedData: feedData, - field: elementField, + field: nativeField ? null : elementField, + nativeField: nativeField ? elementField : null, + fieldClass: fieldClass, feedData: feedData, } only %}