').appendTo(this.$errorList);
- }
-
if (response.errors) {
if (!this.$errorList) {
this.$errorList = $('
').insertAfter(this.$form);
}
for (var attribute in response.errors) {
- for (var i = 0; i < response.errors[attribute].length; i++)
- {
+ for (var i = 0; i < response.errors[attribute].length; i++) {
var error = response.errors[attribute][i];
$('
'+error+'
').appendTo(this.$errorList);
}
@@ -78,7 +82,12 @@ Craft.FeedMeHelp = Garnish.Base.extend({
this.$message.val('');
this.$attachAdditionalFile.val('');
}
+
+ this.$iframe.html('');
}
+},
+{
+ widgets: {}
});
})(jQuery);
diff --git a/feedme/resources/js/FeedMeLicenseForm.js b/feedme/resources/js/FeedMeLicenseForm.js
new file mode 100644
index 00000000..bda00ba9
--- /dev/null
+++ b/feedme/resources/js/FeedMeLicenseForm.js
@@ -0,0 +1,244 @@
+(function($){
+
+if (typeof Craft.FeedMe === typeof undefined) {
+ Craft.FeedMe = {};
+}
+
+Craft.FeedMe.LicenseForm = Craft.BaseElementIndex.extend({
+ licenseKey: null,
+ licenseKeyStatus: null,
+
+ $headers: null,
+ $views: null,
+
+ $validLicenseHeader: null,
+ $invalidLicenseHeader: null,
+ $mismatchedLicenseHeader: null,
+ $unknownLicenseHeader: null,
+
+ $validLicenseView: null,
+ $updateLicenseView: null,
+
+ $unregisterLicenseForm: null,
+ $updateLicenseForm: null,
+ $transferLicenseForm: null,
+
+ $unregisterLicenseSpinner: null,
+ $updateLicenseSpinner: null,
+ $transferLicenseSpinner: null,
+
+ $licenseKeyLabel: null,
+ $licenseKeyInput: null,
+ $updateBtn: null,
+ $clearBtn: null,
+ $licenseKeyError: null,
+
+ init: function(hasLicenseKey) {
+ this.$headers = $('.reg-header');
+ this.$views = $('.reg-view');
+
+ this.$validLicenseHeader = $('#valid-license-header');
+ this.$invalidLicenseHeader = $('#invalid-license-header');
+ this.$mismatchedLicenseHeader = $('#mismatched-license-header');
+ this.$unknownLicenseHeader = $('#unknown-license-header');
+
+ this.$validLicenseView = $('#valid-license-view');
+ this.$updateLicenseView = $('#update-license-view');
+
+ this.$unregisterLicenseForm = $('#unregister-license-form');
+ this.$updateLicenseForm = $('#update-license-form');
+ this.$transferLicenseForm = $('#transfer-license-form');
+
+ this.$unregisterLicenseSpinner = $('#unregister-license-spinner');
+ this.$updateLicenseSpinner = $('#update-license-spinner');
+ this.$transferLicenseSpinner = $('#transfer-license-spinner');
+
+ this.$licenseKeyLabel = $('#license-key-label');
+ this.$licenseKeyInput = $('#license-key-input');
+ this.$updateBtn = $('#update-license-btn');
+ this.$clearBtn = $('#clear-license-btn');
+ this.$licenseKeyError = $('#license-key-error');
+
+ this.addListener(this.$unregisterLicenseForm, 'submit', 'handleUnregisterLicenseFormSubmit');
+ this.addListener(this.$updateLicenseForm, 'submit', 'handleUpdateLicenseFormSubmit');
+ this.addListener(this.$transferLicenseForm, 'submit', 'handleTransferLicenseFormSubmit');
+
+ this.addListener(this.$licenseKeyInput, 'focus', 'handleLicenseKeyFocus');
+ this.addListener(this.$licenseKeyInput, 'textchange', 'handleLicenseKeyTextChange');
+ this.addListener(this.$clearBtn, 'click', 'handleClearButtonClick');
+
+ if (hasLicenseKey) {
+ this.loadLicenseInfo();
+ } else {
+ this.unloadLoadingUi();
+ this.setLicenseKey(null);
+ this.setLicenseKeyStatus('unknown');
+ }
+ },
+
+ unloadLoadingUi: function() {
+ $('#loading-license-info').remove();
+ $('#license-view-hr').removeClass('hidden');
+ },
+
+ loadLicenseInfo: function(action) {
+ Craft.postActionRequest('feedMe/license/getLicenseInfo', $.proxy(function(response, textStatus) {
+ if (textStatus == 'success') {
+ if (response.success) {
+ this.unloadLoadingUi();
+ this.setLicenseKey(response.licenseKey);
+ this.setLicenseKeyStatus(response.licenseKeyStatus);
+ } else {
+ $('#loading-graphic').addClass('error');
+ $('#loading-status').removeClass('light').text(Craft.t('Unable to load registration status at this time. Please try again later.'));
+ }
+ }
+ }, this));
+ },
+
+ setLicenseKey: function(licenseKey) {
+ this.licenseKey = this.normalizeLicenseKey(licenseKey);
+ var formattedLicenseKey = this.formatLicenseKey(this.licenseKey);
+ this.$licenseKeyLabel.text(formattedLicenseKey);
+ this.$licenseKeyInput.val(formattedLicenseKey);
+ this.handleLicenseKeyTextChange();
+ },
+
+ setLicenseKeyStatus: function(licenseKeyStatus) {
+ this.$headers.addClass('hidden');
+ this.$views.addClass('hidden');
+
+ this.licenseKeyStatus = licenseKeyStatus;
+
+ // Show the proper header
+ this['$'+licenseKeyStatus+'LicenseHeader'].removeClass('hidden');
+
+ // Show the proper form view
+ if (this.licenseKeyStatus == 'valid') {
+ this.$validLicenseView.removeClass('hidden');
+ } else {
+ this.$updateLicenseView.removeClass('hidden');
+ this.$licenseKeyError.addClass('hidden');
+
+ if (this.licenseKeyStatus == 'invalid') {
+ this.$licenseKeyInput.addClass('error');
+ } else {
+ this.$licenseKeyInput.removeClass('error');
+ }
+
+ if (this.licenseKeyStatus == 'mismatched') {
+ this.$transferLicenseForm.removeClass('hidden');
+ } else {
+ this.$transferLicenseForm.addClass('hidden');
+ }
+ }
+ },
+
+ normalizeLicenseKey: function(licenseKey) {
+ if (licenseKey) {
+ return licenseKey.toUpperCase().replace(/[^A-Z0-9]/g, '');
+ }
+
+ return '';
+ },
+
+ formatLicenseKey: function(licenseKey) {
+ if (licenseKey) {
+ return licenseKey.match(/.{1,4}/g).join('-');
+ }
+
+ return '';
+ },
+
+ validateLicenseKey: function(licenseKey) {
+ return (licenseKey.length == 24);
+ },
+
+ handleUnregisterLicenseFormSubmit: function(ev) {
+ ev.preventDefault();
+ this.$unregisterLicenseSpinner.removeClass('hidden');
+ Craft.postActionRequest('feedMe/license/unregister', $.proxy(function(response, textStatus) {
+ this.$unregisterLicenseSpinner.addClass('hidden');
+ if (textStatus == 'success') {
+ if (response.success) {
+ this.setLicenseKey(response.licenseKey);
+ this.setLicenseKeyStatus('unknown');
+ } else {
+ Craft.cp.displayError(response.error);
+ }
+ }
+ }, this));
+ },
+
+ handleUpdateLicenseFormSubmit: function(ev) {
+ ev.preventDefault();
+ var licenseKey = this.normalizeLicenseKey(this.$licenseKeyInput.val());
+
+ if (licenseKey && !this.validateLicenseKey(licenseKey)) {
+ return;
+ }
+
+ this.$updateLicenseSpinner.removeClass('hidden');
+
+ var data = {
+ licenseKey: licenseKey
+ };
+
+ Craft.postActionRequest('feedMe/license/updateLicenseKey', data, $.proxy(function(response, textStatus) {
+ this.$updateLicenseSpinner.addClass('hidden');
+ if (textStatus == 'success') {
+ if (response.licenseKey) {
+ this.setLicenseKey(response.licenseKey);
+ this.setLicenseKeyStatus(response.licenseKeyStatus);
+ } else {
+ this.$licenseKeyError.removeClass('hidden').text(response.error || Craft.t('An unknown error occurred.'));
+ }
+ }
+ }, this));
+ },
+
+ handleTransferLicenseFormSubmit: function(ev) {
+ ev.preventDefault();
+ this.$transferLicenseSpinner.removeClass('hidden');
+ Craft.postActionRequest('feedMe/license/transfer', $.proxy(function(response, textStatus) {
+ this.$transferLicenseSpinner.addClass('hidden');
+ if (textStatus == 'success') {
+ if (response.success) {
+ this.setLicenseKey(response.licenseKey);
+ this.setLicenseKeyStatus(response.licenseKeyStatus);
+ } else {
+ Craft.cp.displayError(response.error);
+ }
+ }
+ }, this));
+ },
+
+ handleLicenseKeyFocus: function() {
+ this.$licenseKeyInput.get(0).setSelectionRange(0, this.$licenseKeyInput.val().length);
+ },
+
+ handleLicenseKeyTextChange: function() {
+ this.$licenseKeyInput.removeClass('error');
+
+ var licenseKey = this.normalizeLicenseKey(this.$licenseKeyInput.val());
+
+ if (licenseKey) {
+ this.$clearBtn.removeClass('hidden');
+ } else {
+ this.$clearBtn.addClass('hidden');
+ }
+
+ if (licenseKey != this.licenseKey && (!licenseKey || this.validateLicenseKey(licenseKey))) {
+ this.$updateBtn.removeClass('disabled');
+ } else {
+ this.$updateBtn.addClass('disabled');
+ }
+ },
+
+ handleClearButtonClick: function() {
+ this.$licenseKeyInput.val('').focus();
+ this.handleLicenseKeyTextChange();
+ }
+});
+
+})(jQuery);
diff --git a/feedme/services/FeedMeService.php b/feedme/services/FeedMeService.php
index 6e29fa4d..d457bef8 100644
--- a/feedme/services/FeedMeService.php
+++ b/feedme/services/FeedMeService.php
@@ -6,256 +6,80 @@ class FeedMeService extends BaseApplicationComponent
// Public Methods
// =========================================================================
- public function setupForImport($feed)
+ public function getPlugin()
{
- $return = array(
- 'fields' => array(),
- 'processedEntries' => array(),
- 'existingEntries' => array(),
- );
-
- // Start looping through all the mapped fields - checking for nested nodes
- foreach ($feed['fieldMapping'] as $itemNode => $destination) {
-
- // Forget about any fields mapped as not to import
- if ($destination != 'noimport') {
- $return['fields'][$itemNode] = $destination;
- }
- }
-
- //
- // If our duplication handling is to delete - we delete all entries in this section/entrytype
- //
- if ($feed['duplicateHandle'] == FeedMe_Duplicate::Delete) {
- $criteria = craft()->feedMe_entry->setCriteria($feed);
- $return['existingEntries'] = $criteria->ids();
- }
-
- //
- // Return variables that need to be used per-node, but only need to be processed once.
- //
- return $return;
+ return craft()->plugins->getPlugin('feedMe');
}
- public function importNode($nodes, $feed, $settings)
+ public function getSettings()
{
- $processedEntries = array();
- $hasAnyErrors = false;
-
- $time_start = microtime(true);
- FeedMePlugin::log($feed->name . ': Processing started', LogLevel::Info, true);
-
- foreach ($nodes as $key => $node) {
- $result = $this->importSingleNode($node, $feed, $settings);
-
- if (isset($result['entryId'])) {
- $processedEntries[] = $result['entryId'];
- }
-
- // Report back if even one feed node failed
- if (!$result['result']) {
- $hasAnyErrors = true;
- }
- }
-
- $time_end = microtime(true);
- $execution_time = number_format(($time_end - $time_start), 2);
- FeedMePlugin::log($feed->name . ': Processing finished in ' . $execution_time . 's', LogLevel::Info, true);
-
- return array('result' => !$hasAnyErrors, 'processedEntries' => $processedEntries);
+ return $this->getPlugin()->getSettings();
}
- public function importSingleNode($node, $feed, $settings)
+ public function throwError($feedName, $message)
{
- $canSaveEntry = true;
- $existingEntry = false;
- $fieldData = array();
- $entry = array();
-
- $fields = $settings['fields'];
-
+ FeedMePlugin::log($feedName . ': ' . $message, LogLevel::Error, true);
+ throw new Exception(Craft::t($message));
+ }
- //
- // Lets get started!
- //
-
- $criteria = craft()->feedMe_entry->setCriteria($feed);
-
-
- // Start looping through all the mapped fields - grab their data from the feed node
- foreach ($fields as $itemNode => $handle) {
-
- // Fetch the value for the field from the feed node. Deep-search.
- $data = craft()->feedMe_feed->getValueForNode($itemNode, $node);
-
- // While we're in the loop, lets check for unique data to match existing entries on.
- if (isset($feed['fieldUnique'][$itemNode]) && intval($feed['fieldUnique'][$itemNode]) == 1 && !empty($data)) {
- $criteria->$handle = DbHelper::escapeParam($data);
- }
-
- //
- // Each field needs special processing, sort that out here
- //
-
- try {
- // Grab the field's content - formatted specifically for it
- $content = craft()->feedMe_fields->prepForFieldType($data, $handle);
-
- // The first key of $content will always be the field handle - grab that to create our field data.
- $contentKeys = array_keys($content);
- $fieldHandle = $contentKeys[0];
-
- // Then, we check if we've already got any partial content for the field. Most commongly, this is
- // the case for Matrix and Table fields, but also likely other Third-Party fields. So its important to
- // combine values, rather than overwriting or omitting as each feed node contains just part of the data.
- if (array_key_exists($fieldHandle, $fieldData) && is_array($fieldData[$fieldHandle])) {
- $fieldData[$fieldHandle] = array_replace_recursive($fieldData[$fieldHandle], $content[$fieldHandle]);
- } else {
- $fieldData[$fieldHandle] = $content[$fieldHandle];
+ public function getElementTypeService($elementType)
+ {
+ // Check for third-party element type support
+ $elementsToLoad = craft()->plugins->call('registerFeedMeElementTypes');
+
+ foreach ($elementsToLoad as $plugin => $elementClasses) {
+ foreach ($elementClasses as $elementClass) {
+ if ($elementClass && $elementClass instanceof BaseFeedMeElementType) {
+ if ($elementClass->getElementType() == $elementType) {
+ return $elementClass;
+ }
}
-
- } catch (\Exception $e) {
- FeedMePlugin::log($feed->name . ': FeedMeError: ' . $e->getMessage() . '.', LogLevel::Error, true);
-
- return array('result' => false);
}
}
- $existingEntry = $criteria->first();
-
-
- //
- // Check for Add/Update/Delete for existing entries
- //
-
-
- // If there's an existing matching entry
- if ($existingEntry && $feed['duplicateHandle']) {
-
- // If we're deleting
- if ($feed['duplicateHandle'] == FeedMe_Duplicate::Delete) {
-
- // Fill new EntryModel with match
- $entry = $existingEntry;
- }
-
- // If we're updating
- if ($feed['duplicateHandle'] == FeedMe_Duplicate::Update) {
-
- // Fill new EntryModel with match
- $entry = $existingEntry;
- }
-
- // If we're adding, make sure not to overwrite existing entry
- if ($feed['duplicateHandle'] == FeedMe_Duplicate::Add) {
- $canSaveEntry = false;
- }
- } else {
- // Prepare a new EntryModel (for this section and entrytype)
- $entry = craft()->feedMe_entry->setModel($feed);
- }
-
-
-
- //
- //
- //
-
- if ($canSaveEntry && $entry) {
-
- // Any post-processing on our nice collection of entry-ready data.
- craft()->feedMe_fields->postForFieldType($fieldData, $entry);
-
- // Prepare Element model (the default stuff)
- $entry = craft()->feedMe_entry->prepForElementModel($fieldData, $entry);
-
- // Set our data for this EntryModel (our mapped data)
- if (!$feed['locale']) {
- $entry->setContentFromPost($fieldData);
- }
-
- //echo '
';
- //print_r($fieldData);
- //echo '
';
-
- try {
- // Save the entry!
- if (!craft()->entries->saveEntry($entry)) {
- FeedMePlugin::log($feed->name . ': ' . json_encode($entry->getErrors()), LogLevel::Error, true);
-
- return array('result' => false);
- } else {
-
- // If we're importing into a specific locale, we need to create this entry if it doesn't already exist
- // completely blank of custom field content. After thats saved, we then re-fetch the entry for the specific
- // locale and then add our field data. Doing this ensures its not copied across all locales.
- if ($feed['locale']) {
- $entryLocale = craft()->entries->getEntryById($entry->id, $feed['locale']);
-
- $entryLocale->setContentFromPost($fieldData);
-
- if (!craft()->entries->saveEntry($entryLocale)) {
- FeedMePlugin::log($feed->name . ': ' . json_encode($entryLocale->getErrors()), LogLevel::Error, true);
-
- return array('result' => false);
- } else {
-
- // Successfully saved/added entry
- if ($feed['duplicateHandle'] == FeedMe_Duplicate::Update) {
- FeedMePlugin::log($feed->name . ': Entry successfully updated: ' . $entryLocale->id, LogLevel::Info, true);
- } else {
- FeedMePlugin::log($feed->name . ': Entry successfully added: ' . $entryLocale->id, LogLevel::Info, true);
- }
+ return false;
+ }
- return array('result' => true, 'entryId' => $entryLocale->id);
- }
- } else {
+ public function getDataTypeService($dataType)
+ {
+ // RSS/Atom use XML
+ $dataType = ($dataType == 'rss' || $dataType == 'atom') ? 'xml' : $dataType;
- // Successfully saved/added entry
- if ($feed['duplicateHandle'] == FeedMe_Duplicate::Update) {
- FeedMePlugin::log($feed->name . ': Entry successfully updated: ' . $entry->id, LogLevel::Info, true);
- } else {
- FeedMePlugin::log($feed->name . ': Entry successfully added: ' . $entry->id, LogLevel::Info, true);
- }
+ // Check for third-party data type support
+ $dataToLoad = craft()->plugins->call('registerFeedMeDataTypes');
- return array('result' => true, 'entryId' => $entry->id);
+ foreach ($dataToLoad as $plugin => $dataClasses) {
+ foreach ($dataClasses as $dataClass) {
+ if ($dataClass && $dataClass instanceof BaseFeedMeDataType) {
+ if (StringHelper::toLowercase($dataClass->getDataType()) == $dataType) {
+ return $dataClass;
}
}
- } catch (\Exception $e) {
- FeedMePlugin::log($feed->name . ': Entry FeedMeError: ' . $e->getMessage() . '.', LogLevel::Error, true);
-
- return array('result' => false, 'entryId' => $entry->id);
- }
- } else {
- if ($existingEntry) {
- FeedMePlugin::log($feed->name . ': Entry skipped: ' . $existingEntry->id . '.', LogLevel::Error, true);
}
-
- return array('result' => true);
}
+
+ return false;
}
- public function deleteLeftoverEntries($settings, $feed, $processedEntries, $result)
+ public function getFieldTypeService($fieldType)
{
- if ($feed['duplicateHandle'] == FeedMe_Duplicate::Delete && $result['result']) {
- $deleteIds = array_diff($settings['existingEntries'], $processedEntries);
-
- $criteria = craft()->feedMe_entry->setCriteria($feed);
- $criteria->id = $deleteIds;
- $entriesToDelete = $criteria->find();
-
- try {
- if ($entriesToDelete) {
- if (!craft()->entries->deleteEntry($entriesToDelete)) {
- FeedMePlugin::log('FeedMeError - Something went wrong while deleting entries.', LogLevel::Error, true);
- } else {
- FeedMePlugin::log($feed->name . ': The following entries have been deleted: ' . print_r($deleteIds, true) . '.', LogLevel::Error, true);
+ // Check for third-party field type support
+ $fieldsToLoad = craft()->plugins->call('registerFeedMeFieldTypes');
+
+ foreach ($fieldsToLoad as $plugin => $fieldClasses) {
+ foreach ($fieldClasses as $fieldClass) {
+ if ($fieldClass && $fieldClass instanceof BaseFeedMeFieldType) {
+ if ($fieldClass->getFieldType() == $fieldType) {
+ return $fieldClass;
}
}
- } catch (\Exception $e) {
- FeedMePlugin::log($feed->name . ': FeedMeError: ' . $e->getMessage() . '.', LogLevel::Error, true);
}
}
+
+ // Return default handling for a field
+ return new DefaultFeedMeFieldType();
}
+
+
}
diff --git a/feedme/services/FeedMe_CacheService.php b/feedme/services/FeedMe_CacheService.php
deleted file mode 100644
index d1cfbf98..00000000
--- a/feedme/services/FeedMe_CacheService.php
+++ /dev/null
@@ -1,18 +0,0 @@
-cache->set(base64_encode(urlencode($url)), $value, $duration, null);
- }
-
- public function get($url)
- {
- return craft()->cache->get(base64_encode(urlencode($url)));
- }
-}
\ No newline at end of file
diff --git a/feedme/services/FeedMe_DataService.php b/feedme/services/FeedMe_DataService.php
new file mode 100644
index 00000000..398215d9
--- /dev/null
+++ b/feedme/services/FeedMe_DataService.php
@@ -0,0 +1,269 @@
+config->parseEnvironmentString($url);
+
+ if (!($service = craft()->feedMe->getDataTypeService($type))) {
+ throw new Exception(Craft::t('Unknown Data Type Service called.'));
+ }
+
+ $data = $service->getFeed($url, $element, $settings);
+
+ if (!isset($data[0])) {
+ $data = array($data);
+ }
+
+ if (empty($data[0])) {
+ return null;
+ } else {
+ return $data;
+ }
+ }
+
+ public function getFeedMapping($type, $url, $element, $settings) {
+ $data_array = $this->getFeed($type, $url, $element, $settings);
+
+ // Go through entire feed and grab all nodes - that way, its normalised across the entire feed
+ // as some nodes don't exist on the first primary element, but do throughout the feed.
+ $array = array();
+
+ if ($data_array) {
+ foreach ($data_array as $data_array_item) {
+ $variable = Hash::flatten($data_array_item);
+
+ foreach ($variable as $key => $value) {
+ // Assets.Asset.0.Img.0 = Assets/Asset/.../Img[]
+ $string = str_replace('.', '/', $key);
+ $string = preg_replace('/(\/\d+\/)/', '/.../', $string);
+ $string = preg_replace('/(\/\d+)/', '[]', $string);
+
+ if (!isset($array[$string])) {
+ $array[$string] = $value;
+ }
+ }
+
+ //$array = $array + $this->_getFormattedMapping($data_array_item);
+ }
+
+ // Then - a little bit of post-processing to deal with inconsistent nodes
+ // XML in particular doesn't allow you to specifically state if there are multiple nodes
+ // For example, we have no way of detecting this sort of data:
+ //
+ //
+ // image_1.jpg
+ //
+ //
+ // image_2.jpg
+ //
+ //
+ //
+ //
+ // image_3.jpg
+ //
+ //
+ // This ends up producing:
+ // [Assets/Asset/.../Img] => image_1.jpg
+ // [Assets/Asset/Img] => image_3.jpg
+ foreach ($array as $key => $value) {
+ $keys = explode('/', $key);
+
+ // Loop through each element, adding a '/.../' at various positions.
+ // We simply can't rely on it being in a specific condition, due to each field being different
+ if (count($keys) > 1) {
+ for ($i = 0; $i < count($keys); $i++) {
+ $temp = explode('/', $key);
+ array_splice($temp, $i, 0, '...');
+ $potentialKey = implode('/', $temp);
+
+ if (isset($array[$potentialKey])) {
+ unset($array[$key]);
+ }
+ }
+ }
+ }
+ }
+
+ return $array;
+ }
+
+ public function findPrimaryElement($element, $parsed) {
+ if (empty($parsed)) {
+ return false;
+ }
+
+ // If no primary element, return root
+ if (!$element) {
+ return $parsed;
+ }
+
+ if (isset($parsed[$element])) {
+ // Ensure we return an array - even if only one element found
+ if (is_array($parsed[$element])) {
+ if (array_key_exists('0', $parsed[$element])) { // is multidimensional
+ return $parsed[$element];
+ } else {
+ return array($parsed[$element]);
+ }
+ }
+ }
+
+ foreach ($parsed as $key => $val) {
+ if (is_array($val)) {
+ $return = $this->findPrimaryElement($element, $val);
+
+ if ($return !== false) {
+ return $return;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public function getRawData($url)
+ {
+ // Check for local URL
+ if (!UrlHelper::isAbsoluteUrl($url)) {
+ return file_get_contents($url);
+ }
+
+ $curl = curl_init();
+
+ $defaultOptions = array(
+ CURLOPT_URL => $url,
+ CURLOPT_RETURNTRANSFER => 1,
+ CURLOPT_FOLLOWLOCATION => 1,
+ CURLOPT_SSL_VERIFYPEER => 0,
+ CURLOPT_USERAGENT => craft()->plugins->getPlugin('feedMe')->getName(),
+ );
+
+ $configOptions = craft()->config->get('curlOptions', 'feedMe');
+
+ if ($configOptions) {
+ $options = $configOptions + $defaultOptions;
+ } else {
+ $options = $defaultOptions;
+ }
+
+ curl_setopt_array($curl, $options);
+ $response = curl_exec($curl);
+
+ $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+
+ if (!$response) {
+ FeedMePlugin::log($url . ' response: ' . print_r($response, true), LogLevel::Error, true);
+ FeedMePlugin::log(curl_error($curl), LogLevel::Error, true);
+
+ return false;
+ } else {
+ if ($httpCode != 200 && $httpCode != 226) {
+ FeedMePlugin::log($url . ' responded with code ' . $httpCode, LogLevel::Error, true);
+
+ return false;
+ }
+ }
+
+ curl_close($curl);
+
+ return $response;
+ }
+
+ public function getFeedForTemplate($options = array())
+ {
+ $plugin = craft()->plugins->getPlugin('feedMe');
+ $settings = $plugin->getSettings();
+
+ $url = (array_key_exists('url', $options) ? $options['url'] : null);
+ $type = (array_key_exists('type', $options) ? $options['type'] : FeedMe_FeedType::XML);
+ $element = (array_key_exists('element', $options) ? $options['element'] : '');
+ $cache = (array_key_exists('cache', $options) ? $options['cache'] : true);
+ $cacheId = $url . '#' . $element; // cache for this URL and Element Node
+
+ // URL = required
+ if (!$url) {
+ return array();
+ }
+
+ // If cache explicitly set to false, always return latest data
+ if ($cache === false) {
+ return craft()->feedMe_data->getFeed($type, $url, $element, null);
+ }
+
+ // We want some caching action!
+ if (is_numeric($cache) || $cache === true) {
+ $cache = (is_numeric($cache)) ? $cache : $settings->cache;
+
+ $cachedRequest = $this->_get($cacheId);
+
+ if ($cachedRequest) {
+ return $cachedRequest;
+ } else {
+ $data = craft()->feedMe_data->getFeed($type, $url, $element, null);
+ $this->_set($cacheId, $data, $cache);
+
+ return $data;
+ }
+ }
+ }
+
+
+
+ // Private Methods
+ // =========================================================================
+
+ private function _getFormattedMapping($data, $sep = '') {
+ $return = array();
+
+ if ($sep != '') {
+ $sep .= '/';
+ }
+
+ if (!is_array($data)) {
+ return $data;
+ }
+
+ foreach ($data as $key => $value) {
+ if (!is_array($value)) {
+ $return[$sep . $key] = $value;
+ } elseif (count($value) == 0) {
+ $return[$sep . $key . '/...'] = array();
+ } elseif (isset($value[0])) {
+ if (is_string($value[0]) || is_numeric($value[0])) {
+ $return[$sep . $key . '[]'] = $value[0];
+ } else {
+ foreach ($value as $v) {
+ $nested = $this->_getFormattedMapping($v, $sep . $key . '/...');
+
+ if (is_array($nested)) {
+ $return = array_merge($return, $nested);
+ }
+ }
+ }
+ } else {
+ $return = array_merge($return, $this->_getFormattedMapping($value, $sep . $key));
+ }
+ }
+
+ return $return;
+ }
+
+ private function _set($url, $value, $duration)
+ {
+ return craft()->cache->set(base64_encode(urlencode($url)), $value, $duration, null);
+ }
+
+ private function _get($url)
+ {
+ return craft()->cache->get(base64_encode(urlencode($url)));
+ }
+
+}
diff --git a/feedme/services/FeedMe_EntryService.php b/feedme/services/FeedMe_EntryService.php
deleted file mode 100644
index 90a2b633..00000000
--- a/feedme/services/FeedMe_EntryService.php
+++ /dev/null
@@ -1,164 +0,0 @@
-sections->getEditableSections();
-
- // Get sections but not singles
- $sections = array();
- foreach ($editable as $section) {
- if ($section->type != SectionType::Single) {
- $sections[] = $section;
- }
- }
-
- return $sections;
- }
-
- public function setModel($settings)
- {
- // Set up new entry model
- $element = new EntryModel();
- $element->sectionId = $settings['section'];
- $element->typeId = $settings['entrytype'];
-
- if ($settings['locale']) {
- $element->locale = $settings['locale'];
- }
-
- return $element;
- }
-
- public function setCriteria($settings)
- {
- // Match with current data
- $criteria = craft()->elements->getCriteria(ElementType::Entry);
- $criteria->limit = null;
- $criteria->localeEnabled = null;
- $criteria->status = isset($settings['fieldMapping']['status']) ? $settings['fieldMapping']['status'] : null;
-
- // Look in same section when replacing
- $criteria->sectionId = $settings['section'];
- $criteria->type = $settings['entrytype'];
-
- if ($settings['locale']) {
- $criteria->locale = $settings['locale'];
- }
-
- return $criteria;
- }
-
- // Prepare reserved ElementModel values
- public function prepForElementModel(&$fields, EntryModel $element)
- {
- // Set author
- $author = FeedMe_Element::Author;
- if (isset($fields[$author])) {
- $criteria = craft()->elements->getCriteria(ElementType::User);
- $criteria->search = $fields[$author];
- $authorUser = $criteria->first();
-
- if ($authorUser) {
- $element->$author = $authorUser->id;
- } else {
- $user = craft()->users->getUserByUsernameOrEmail($fields[$author]);
- $element->$author = (is_numeric($fields[$author]) ? $fields[$author] : ($user ? $user->id : 1));
- }
- } else {
- $user = craft()->userSession->getUser();
- $element->$author = ($element->$author ? $element->$author : ($user ? $user->id : 1));
- }
-
- // Set slug
- $slug = FeedMe_Element::Slug;
- if (isset($fields[$slug])) {
- $element->$slug = ElementHelper::createSlug($fields[$slug]);
- }
-
- // Set postdate
- $postDate = FeedMe_Element::PostDate;
- if (isset($fields[$postDate])) {
- $d = date_parse($fields[$postDate]);
- $date_string = date('Y-m-d H:i:s', mktime($d['hour'], $d['minute'], $d['second'], $d['month'], $d['day'], $d['year']));
-
- $element->$postDate = DateTime::createFromString($date_string, craft()->timezone);
- }
-
- // Set expiry date
- $expiryDate = FeedMe_Element::ExpiryDate;
- if (isset($fields[$expiryDate])) {
- $d = date_parse($fields[$expiryDate]);
- $date_string = date('Y-m-d H:i:s', mktime($d['hour'], $d['minute'], $d['second'], $d['month'], $d['day'], $d['year']));
-
- $element->$expiryDate = DateTime::createFromString($date_string, craft()->timezone);
- }
-
- // Set enabled
- $enabled = FeedMe_Element::Enabled;
- if (isset($fields[$enabled])) {
- $element->$enabled = (bool) $fields[$enabled];
- }
-
- // Set title
- $title = FeedMe_Element::Title;
- if (isset($fields[$title])) {
- $element->getContent()->$title = $fields[$title];
- }
-
- // Set parent or ancestors
- $parent = FeedMe_Element::Parent;
- $ancestors = FeedMe_Element::Ancestors;
-
- if (isset($fields[$parent])) {
- $data = $fields[$parent];
-
- // Don't connect empty fields
- if (!empty($data)) {
-
- // Find matching element
- $criteria = craft()->elements->getCriteria(ElementType::Entry);
- $criteria->sectionId = $element->sectionId;
- $criteria->search = '"'.$data.'"';
-
- // Return the first found element for connecting
- if ($criteria->total()) {
- $element->$parent = $criteria->first()->id;
- }
- }
- } elseif (isset($fields[$ancestors])) {
- $data = $fields[$ancestors];
-
- // Don't connect empty fields
- if (!empty($data)) {
-
- // Get section data
- $section = new SectionModel();
- $section->id = $element->sectionId;
-
- // This we append before the slugified path
- $sectionUrl = str_replace('{slug}', '', $section->getUrlFormat());
-
- // Find matching element by URI (dirty, not all structures have URI's)
- $criteria = craft()->elements->getCriteria(ElementType::Entry);
- $criteria->sectionId = $element->sectionId;
- //$criteria->uri = $sectionUrl.craft()->feedMe->slugify($data);
- $criteria->limit = 1;
-
- // Return the first found element for connecting
- if ($criteria->total()) {
- $element->$parent = $criteria->first()->id;
- }
- }
- }
-
- // Return element
- return $element;
- }
-}
diff --git a/feedme/services/FeedMe_FeedJSONService.php b/feedme/services/FeedMe_FeedJSONService.php
deleted file mode 100644
index 6566175b..00000000
--- a/feedme/services/FeedMe_FeedJSONService.php
+++ /dev/null
@@ -1,53 +0,0 @@
-feedMe_feed->getRawData($url))) {
- craft()->userSession->setError(Craft::t('Unable to parse Feed URL.'));
- FeedMePlugin::log('Unable to parse Feed URL.', LogLevel::Error, true);
-
- return false;
- }
-
- // Parse the JSON string - using Yii's built-in cleanup
- $json_array = JsonHelper::decode($raw_content, true);
-
- // Look for and return only the items for primary element
- $json_array = craft()->feedMe_feed->findPrimaryElement($primaryElement, $json_array);
-
- if (!is_array($json_array)) {
- $error = 'Invalid JSON - ' . $this->getJsonError();
-
- craft()->userSession->setError(Craft::t($error));
- FeedMePlugin::log($error, LogLevel::Error, true);
-
- return false;
- }
-
- return $json_array;
- }
-
- public function getJsonError()
- {
- if (!function_exists('json_last_error_msg')) {
- $errors = array(
- JSON_ERROR_NONE => null,
- JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
- JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch',
- JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
- JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
- JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
- );
-
- $error = json_last_error();
- return array_key_exists($error, $errors) ? $errors[$error] : "Unknown error ({$error})";
- } else {
- return json_last_error_msg();
- }
- }
-}
diff --git a/feedme/services/FeedMe_FeedService.php b/feedme/services/FeedMe_FeedService.php
deleted file mode 100644
index 13ddb302..00000000
--- a/feedme/services/FeedMe_FeedService.php
+++ /dev/null
@@ -1,212 +0,0 @@
-config->parseEnvironmentString($url);
-
- if ($type == FeedMe_FeedType::JSON) {
- return craft()->feedMe_feedJSON->getFeed($url, $element);
- } else {
- return craft()->feedMe_feedXML->getFeed($url, $element, $returnAttr);
- }
- }
-
- public function getFeedMapping($type, $url, $element) {
- $array = $this->getFeed($type, $url, $element);
-
- if (isset($array[0])) {
- $array = $this->getFormattedMapping($array[0]);
- } else {
- $array = $this->getFormattedMapping($array);
- }
-
- return $array;
- }
-
- function findPrimaryElement($element, $parsed) {
- if (empty($parsed)) {
- return false;
- }
-
- // If no primary element, return root
- if (!$element) {
- return $parsed;
- }
-
- if (isset($parsed[$element])) {
- // Ensure we return an array - even if only one element found
- if (is_array($parsed[$element])) {
- if (array_key_exists('0', $parsed[$element])) { // is multidimensional
- return $parsed[$element];
- } else {
- return array($parsed[$element]);
- }
- }
- }
-
- foreach ($parsed as $key => $val) {
- if (is_array($val)) {
- $return = $this->findPrimaryElement($element, $val);
-
- if ($return !== false) {
- return $return;
- }
- }
- }
-
- return false;
- }
-
- public function getFormattedMapping($data, $sep = '') {
- $return = array();
-
- if ($sep != '') {
- $sep .= '/';
- }
-
- if (!is_array($data)) {
- return $data;
- }
-
- foreach($data as $key => $value) {
- if (!is_array($value)) {
- $return[$sep . $key] = $value;
- } elseif (count($value) == 0) {
- $return[$sep . $key . '/...'] = array();
- } elseif(isset($value[0])) {
- if (is_string($value[0]) || is_numeric($value[0])) {
- $return[$sep . $key] = $value[0];
- } else {
- $return = array_merge($return, $this->getFormattedMapping($value[0], $sep . $key.'/...'));
- }
- } else {
- $return = array_merge($return, $this->getFormattedMapping($value, $sep . $key));
- }
- }
-
- return $return;
- }
-
- public function getValueForNode($element, $data)
- {
- if (empty($data)) {
- return null;
- }
-
- if (!is_string($element) || $element == '') {
- return null;
- }
-
- if (stristr($element, '/')) {
- $original_data = $data;
-
- $indexes = explode('/', $element);
-
- while (count($indexes) > 0) {
- $elementNode = array_shift($indexes);
-
- if ($elementNode === '...') {
- if (is_array($data)) {
-
- if (!isset($data[0])) {
- $data = array($data);
- }
-
- $next = array_shift($indexes);
-
- if (!isset($next)) {
- return $data;
- }
-
- $next_data = array();
-
- foreach($data as $subkey => $subvalue) {
- unset($data[$subkey]);
- $next_element = $this->getValueForNode($next, $subvalue);
-
- //if (!empty($next_element)) {
- $next_data[] = $next_element;
- //}
- }
-
- return (empty($next_data)) ? false : $next_data;
- } else {
- $data = (is_string($data)) ? $data : null;
- }
- } else {
- $data = $this->getValueForNode($elementNode, $data);
- }
-
- if ($data === null) {
- break;
- }
- }
-
- if ($data !== false) {
- return $data;
- }
-
- $data = $original_data;
- }
-
- if (isset($data[$element])) {
- if (is_array($data[$element])) {
- if (count($data[$element]) == 0) {
- return '';
- }
-
- if (isset($data[$element][0])) {
- return $data[$element];
- }
- }
-
- return $data[$element];
- }
-
- if (is_array($data)) {
- foreach ($data as $key => $val) {
- if (is_array($val)) {
- $return = $this->getValueForNode($element, $val);
-
- if ($return !== null) {
- if (is_array($return) && isset($return[0])) {
- return $return;
- }
-
- return $return;
- }
- }
- }
- }
-
- return null;
- }
-
- public function getRawData($url)
- {
- $curl = curl_init();
- curl_setopt($curl, CURLOPT_URL, $url);
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($curl, CURLOPT_USERAGENT, craft()->plugins->getPlugin('feedMe')->getName());
- $response = curl_exec($curl);
-
- if (!$response) {
- FeedMePlugin::log(print_r($response, true), LogLevel::Error, true);
- FeedMePlugin::log(curl_error($curl), LogLevel::Error, true);
- return false;
- }
-
- curl_close($curl);
-
- return $response;
- }
-
-}
diff --git a/feedme/services/FeedMe_FeedXMLService.php b/feedme/services/FeedMe_FeedXMLService.php
deleted file mode 100644
index 5f754f6c..00000000
--- a/feedme/services/FeedMe_FeedXMLService.php
+++ /dev/null
@@ -1,124 +0,0 @@
-feedMe_feed->getRawData($url))) {
- craft()->userSession->setError(Craft::t('Unable to parse Feed URL.'));
- FeedMePlugin::log('Unable to parse Feed URL.', LogLevel::Error, true);
-
- return false;
- }
-
- // Perform cleanup on raw data first
- //$raw_content = preg_replace("/[\r\n]+/", " ", $raw_content);
- //$xml = stripslashes($raw_content);
- //$xml = utf8_encode($xml);
- //$xml = StringHelper::convertToUTF8($xml);
-
- // Parse the XML string
- $xml_array = $this->parseXML($raw_content);
-
- // Convert it to an array
- $xml_array = $this->elementArray($xml_array, true, $returnAttr);
-
- // Look for and return only the items for primary element
- $xml_array = craft()->feedMe_feed->findPrimaryElement($primaryElement, $xml_array);
-
- if (!is_array($xml_array)) {
- craft()->userSession->setError(Craft::t('Invalid XML.'));
- FeedMePlugin::log('Invalid XML.', LogLevel::Error, true);
-
- return false;
- }
-
- return $xml_array;
- }
-
- public function elementArray($xml, $first = true, $returnAttr = false)
- {
- if (!$xml) {
- return null;
- }
-
- if (empty($xml->children)) {
- if ($returnAttr) {
- // Used when calling via template code - return the attributes for the node
- $nodeModel = new FeedMe_FeedNodeModel();
- $nodeModel->attributes = $xml->attributes;
- $nodeModel->value = $xml->value;
- $return = $nodeModel;
- } else {
- $return = $xml->value;
- }
- } else {
- $return = array();
- foreach($xml->children as $child) {
- $child->tag = strtolower($child->tag);
-
- if (isset($return[$child->tag])) {
- if (!is_array($return[$child->tag]) OR !isset($return[$child->tag][0])) {
- $return[$child->tag] = array(0 => $return[$child->tag]);
- }
-
- $return[$child->tag][] = $this->elementArray($child, false, $returnAttr);
- } else {
- $return[$child->tag] = $this->elementArray($child, false, $returnAttr);
- }
- }
- }
-
- if ($first === false) {
- return $return;
- }
-
- $return = array($xml->tag => $return);
-
- return $return;
- }
-
- public function parseXML($xml)
- {
- $xmlArray = null;
-
- $parser = xml_parser_create();
- xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
- xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
- xml_parse_into_struct($parser, $xml, $xmlArray, $indexdata);
- xml_parser_free($parser);
-
- $elements = array();
- $child = array();
- foreach ($xmlArray as $item) {
- $current = count($elements);
-
- if ($item['type'] == 'open' || $item['type'] == 'complete') {
- $elements[$current] = new \stdClass;
- $elements[$current]->tag = $item['tag'];
- $elements[$current]->attributes = (array_key_exists('attributes', $item)) ? $item['attributes'] : '';
- $elements[$current]->value = (array_key_exists('value', $item)) ? $item['value'] : '';
-
- if ($item['type'] == "open") {
- $elements[$current]->children = array();
- $child[count($child)] = &$elements;
- $elements = &$elements[$current]->children;
- }
- } else if ($item['type'] == 'close') {
- $elements = &$child[count($child) - 1];
- unset($child[count($child) - 1]);
- }
- }
-
- if ($elements) {
- return $elements[0];
- } else {
- return null;
- }
- }
-
-}
diff --git a/feedme/services/FeedMe_FeedsService.php b/feedme/services/FeedMe_FeedsService.php
index fdc0093d..891eab16 100644
--- a/feedme/services/FeedMe_FeedsService.php
+++ b/feedme/services/FeedMe_FeedsService.php
@@ -20,48 +20,7 @@ public function getTotalFeeds()
public function getFeedById($feedId)
{
$feedRecord = FeedMe_FeedRecord::model()->findById($feedId);
- if ($feedRecord) {
- return FeedMe_FeedModel::populateModel($feedRecord);
- }
- return null;
- }
-
- public function getFeedForTemplate($options = array())
- {
- $plugin = craft()->plugins->getPlugin('feedMe');
- $settings = $plugin->getSettings();
-
- $url = (array_key_exists('url', $options) ? $options['url'] : null);
- $type = (array_key_exists('type', $options) ? $options['type'] : FeedMe_FeedType::XML);
- $element = (array_key_exists('element', $options) ? $options['element'] : '');
- $cache = (array_key_exists('cache', $options) ? $options['cache'] : true);
- $cacheId = $url . '#' . $element; // cache for this URL and Element Node
-
- // URL = required
- if (!$url) {
- return array();
- }
-
- // If cache explicitly set to false, always return latest data
- if ($cache === false) {
- return craft()->feedMe_feed->getFeed($type, $url, $element, true);
- }
-
- // We want some caching action!
- if (is_numeric($cache) || $cache === true) {
- $cache = (is_numeric($cache)) ? $cache : $settings->cache;
-
- $cachedRequest = craft()->feedMe_cache->get($cacheId);
-
- if ($cachedRequest) {
- return $cachedRequest;
- } else {
- $data = craft()->feedMe_feed->getFeed($type, $url, $element, true);
- craft()->feedMe_cache->set($cacheId, $data, $cache);
-
- return $data;
- }
- }
+ return FeedMe_FeedModel::populateModel($feedRecord);
}
public function saveFeed(FeedMe_FeedModel $feed)
@@ -81,17 +40,32 @@ public function saveFeed(FeedMe_FeedModel $feed)
$feedRecord->feedUrl = $feed->feedUrl;
$feedRecord->feedType = $feed->feedType;
$feedRecord->primaryElement = $feed->primaryElement;
- $feedRecord->section = $feed->section;
- $feedRecord->entrytype = $feed->entrytype;
+ $feedRecord->elementType = $feed->elementType;
$feedRecord->locale = $feed->locale;
$feedRecord->duplicateHandle = $feed->duplicateHandle;
$feedRecord->passkey = $feed->passkey;
$feedRecord->backup = $feed->backup;
+ if ($feed->elementGroup) {
+ $feedRecord->setAttribute('elementGroup', json_encode($feed->elementGroup));
+ }
+
if ($feed->fieldMapping) {
$feedRecord->setAttribute('fieldMapping', json_encode($feed->fieldMapping));
}
+ if ($feed->fieldDefaults) {
+ $feedRecord->setAttribute('fieldDefaults', json_encode($feed->fieldDefaults));
+ }
+
+ if ($feed->fieldElementMapping) {
+ $feedRecord->setAttribute('fieldElementMapping', json_encode($feed->fieldElementMapping));
+ }
+
+ if ($feed->fieldElementDefaults) {
+ $feedRecord->setAttribute('fieldElementDefaults', json_encode($feed->fieldElementDefaults));
+ }
+
if ($feed->fieldUnique) {
$feedRecord->setAttribute('fieldUnique', json_encode($feed->fieldUnique));
}
@@ -105,17 +79,18 @@ public function saveFeed(FeedMe_FeedModel $feed)
if ($feedRecord->save()) {
// Update Model with ID from Database
- $feed->setAttribute('id', $feedRecord->getAttribute('id'));
- $feed->setAttribute('fieldMapping', $feedRecord->getAttribute('fieldMapping'));
- $feed->setAttribute('fieldUnique', $feedRecord->getAttribute('fieldUnique'));
+ $feed->id = $feedRecord->id;
+ $feed->fieldMapping = $feedRecord->fieldMapping;
+ $feed->fieldDefaults = $feedRecord->fieldDefaults;
+ $feed->fieldElementMapping = $feedRecord->fieldElementMapping;
+ $feed->fieldElementDefaults = $feedRecord->fieldElementDefaults;
+ $feed->fieldUnique = $feedRecord->fieldUnique;
return true;
} else {
$feed->addErrors($feedRecord->getErrors());
return false;
}
- } else {
- //die(print_r($feed->getErrors()));
}
return false;
diff --git a/feedme/services/FeedMe_FieldsService.php b/feedme/services/FeedMe_FieldsService.php
index 496e34fc..6211a62e 100644
--- a/feedme/services/FeedMe_FieldsService.php
+++ b/feedme/services/FeedMe_FieldsService.php
@@ -1,510 +1,90 @@
_entryFields[$fieldHandle])) {
- $field = $this->_entryFields[$fieldHandle];
- } else {
- $field = craft()->fields->getFieldByHandle($fieldHandle);
-
- $this->_entryFields[$fieldHandle] = $field;
- }
- } else {
- $fieldHandle = $handle;
- }
-
- if (!is_null($field)) {
- switch ($field->type) {
- case FeedMe_FieldType::Assets:
- $data = array( $fieldHandle => $this->prepAssets($data, $field) ); break;
- case FeedMe_FieldType::Categories:
- $data = array( $fieldHandle => $this->prepCategories($data, $field) ); break;
- case FeedMe_FieldType::Checkboxes:
- $data = array( $fieldHandle => $this->prepCheckboxes($data, $field) ); break;
- case FeedMe_FieldType::Date:
- $data = array( $fieldHandle => $this->prepDate($data, $field) ); break;
- case FeedMe_FieldType::Dropdown:
- $data = array( $fieldHandle => $this->prepDropdown($data, $field) ); break;
- case FeedMe_FieldType::Entries:
- $data = array( $fieldHandle => $this->prepEntries($data, $field) ); break;
- case FeedMe_FieldType::Matrix:
- $data = array( $fieldHandle => $this->prepMatrix($data, $handle, $field) ); break;
- case FeedMe_FieldType::MultiSelect:
- $data = array( $fieldHandle => $this->prepMultiSelect($data, $field) ); break;
- case FeedMe_FieldType::Number:
- $data = array( $fieldHandle => $this->prepNumber($data, $field) ); break;
- case FeedMe_FieldType::RadioButtons:
- $data = array( $fieldHandle => $this->prepRadioButtons($data, $field) ); break;
- case FeedMe_FieldType::RichText:
- $data = array( $fieldHandle => $this->prepRichText($data, $field) ); break;
- case FeedMe_FieldType::Table:
- $data = array( $fieldHandle => $this->prepTable($data, $handle, $field) ); break;
- case FeedMe_FieldType::Tags:
- $data = array( $fieldHandle => $this->prepTags($data, $field) ); break;
- case FeedMe_FieldType::Users:
- $data = array( $fieldHandle => $this->prepUsers($data, $field) ); break;
-
- // Color, Lightswitch, PlainText, PositionSelect all take care of themselves
- default:
- $data = array( $fieldHandle => $data );
- }
-
- // Third-party fieldtype support
- craft()->plugins->call('prepForFeedMeFieldType', array($field, &$data, $handle));
- } else {
- // For core entry fields - still need to return with handle
- $data = array( $fieldHandle => $data );
- }
-
- return $data;
- }
-
- public function prepAssets($data, $field) {
- $fieldData = array();
-
- if (!empty($data)) {
- $settings = $field->getFieldType()->getSettings();
-
- // Get source id's for connecting
- $sourceIds = array();
- $sources = $settings->sources;
- if (is_array($sources)) {
- foreach ($sources as $source) {
- list($type, $id) = explode(':', $source);
- $sourceIds[] = $id;
- }
- }
-
- // Find matching element in sources
- $criteria = craft()->elements->getCriteria(ElementType::Asset);
- $criteria->sourceId = $sourceIds;
- $criteria->limit = $settings->limit;
-
- // Get search strings
- $search = ArrayHelper::stringToArray($data);
-
- // Loop through keywords
- foreach ($search as $query) {
- $criteria->search = $query;
-
- $fieldData = array_merge($fieldData, $criteria->ids());
- }
- }
-
- // Check for field limit - only return the specified amount
- if ($fieldData) {
- if ($field->settings['limit']) {
- $fieldData = array_chunk($fieldData, $field->settings['limit']);
- $fieldData = $fieldData[0];
- }
- }
-
- return $fieldData;
- }
-
- public function prepCategories($data, $field) {
- $fieldData = array();
-
- if (!empty($data)) {
- $settings = $field->getFieldType()->getSettings();
-
- // Get category group id
- $source = $settings->getAttribute('source');
- list($type, $groupId) = explode(':', $source);
-
- $categories = ArrayHelper::stringToArray($data);
-
- foreach ($categories as $category) {
- $categoryArray = array();
-
- if (!empty($category)) {
-
- // Find existing category
- $criteria = craft()->elements->getCriteria(ElementType::Category);
- $criteria->title = DbHelper::escapeParam($category);
- $criteria->groupId = $groupId;
- $criteria->limit = 1;
-
- if (!$criteria->total()) {
- // Create category if one doesn't already exist
- $newCategory = new CategoryModel();
- $newCategory->getContent()->title = $category;
- $newCategory->groupId = $groupId;
-
- // Save category
- if (craft()->categories->saveCategory($newCategory)) {
- $categoryArray = array($newCategory->id);
- }
- } else {
- $categoryArray = $criteria->ids();
- }
- }
-
- // Add categories to data array
- $fieldData = array_merge($fieldData, $categoryArray);
- }
- }
-
- // Check for field limit - only return the specified amount
- if ($fieldData) {
- if ($field->settings['limit']) {
- $fieldData = array_chunk($fieldData, $field->settings['limit']);
- $fieldData = $fieldData[0];
- }
- }
-
- return $fieldData;
- }
-
- public function prepCheckboxes($data, $field) {
- return ArrayHelper::stringToArray($data);
- }
-
- public function prepDate($data, $field) {
- return DateTimeHelper::formatTimeForDb(DateTimeHelper::fromString($data, craft()->timezone));
- }
-
- public function prepDropdown($data, $field) {
- $fieldData = null;
-
- $settings = $field->getFieldType()->getSettings();
- $options = $settings->getAttribute('options');
-
- // find matching option label
- foreach ($options as $option) {
- if ($data == $option['value']) {
- $fieldData = $option['value'];
- break;
- }
- }
-
- return $fieldData;
- }
-
- public function prepEntries($data, $field) {
- $fieldData = array();
-
- if (!empty($data)) {
- $settings = $field->getFieldType()->getSettings();
-
- // Get source id's for connecting
- $sectionIds = array();
- $sources = $settings->sources;
- if (is_array($sources)) {
- foreach ($sources as $source) {
- // When singles is selected as the only option to search in, it doesn't contain any ids...
- if ($source == 'singles') {
- foreach (craft()->sections->getAllSections() as $section) {
- $sectionIds[] = ($section->type == 'single') ? $section->id : '';
- }
- } else {
- list($type, $id) = explode(':', $source);
- $sectionIds[] = $id;
- }
- }
- }
-
- $entries = ArrayHelper::stringToArray($data);
-
- foreach ($entries as $entry) {
- $criteria = craft()->elements->getCriteria(ElementType::Entry);
- $criteria->sectionId = $sectionIds;
- $criteria->limit = $settings->limit;
- $criteria->search = 'title:'.$entry.' OR slug:'.$entry;
-
- $fieldData = array_merge($fieldData, $criteria->ids());
- }
- }
-
- // Check for field limit - only return the specified amount
- if ($fieldData) {
- if ($field->settings['limit']) {
- $fieldData = array_chunk($fieldData, $field->settings['limit']);
- $fieldData = $fieldData[0];
- }
- }
-
- return $fieldData;
- }
-
- public function prepMatrix($data, $handle, $field) {
- $fieldData = array();
-
- preg_match_all('/\w+/', $handle, $matches);
-
- if (isset($matches[0])) {
- $fieldHandle = $matches[0][0];
- $blocktypeHandle = $matches[0][1];
- $subFieldHandle = $matches[0][2];
-
- // Store the fields for this Matrix - can't use the fields service due to context
- $blockTypes = craft()->matrix->getBlockTypesByFieldId($field->id, 'handle');
- $blockType = $blockTypes[$blocktypeHandle];
-
- foreach ($blockType->getFields() as $f) {
- if ($f->handle == $subFieldHandle) {
- $subField = $f;
- }
- }
-
- $rows = array();
-
- if (!empty($data)) {
- if (!is_array($data)) {
- $data = array($data);
- }
-
- // Additional check for Table or other 'special' nested fields
- if (count($matches[0]) > 3) {
- $filteredSubFieldHandle = $matches[0][2] . '[' . $matches[0][3] . ']';
- } else {
- $filteredSubFieldHandle = $subFieldHandle;
- }
-
- foreach ($data as $i => $singleFieldData) {
- $subFieldData = $this->prepForFieldType($singleFieldData, $filteredSubFieldHandle, $subField);
-
- if (count($matches[0]) > 3) {
- $subFieldData = array($subFieldHandle => $subFieldData[$filteredSubFieldHandle]);
- }
-
- $fieldData['new'.$blocktypeHandle.($i+1)] = array(
- 'type' => $blocktypeHandle,
- 'order' => $i,
- 'enabled' => true,
- 'fields' => $subFieldData,
- );
- }
- }
- }
-
- return $fieldData;
- }
-
- public function prepMultiSelect($data, $field) {
- return ArrayHelper::stringToArray($data);
+ // Load all fieldtypes once
+ $this->_fields = craft()->fields->getAllFields('handle');
}
- public function prepNumber($data, $field) {
- return floatval(LocalizationHelper::normalizeNumber($data));
- }
+ public function prepForFieldType($element, $data, $handle, $options = array())
+ {
+ $field = null;
- public function prepRichText($data, $field) {
- if (is_array($data)) {
- return implode($data);
+ if (isset($options['field'])) {
+ $field = $options['field'];
} else {
- return $data;
- }
- }
-
- public function prepRadioButtons($data, $field) {
- $fieldData = null;
-
- $settings = $field->getFieldType()->getSettings();
- $options = $settings->getAttribute('options');
-
- // find matching option label
- foreach ($options as $option) {
- if ($data == $option['value']) {
- $fieldData = $option['value'];
- break;
+ if (isset($this->_fields[$handle])) {
+ $field = $this->_fields[$handle];
}
}
- return $fieldData;
- }
-
- public function prepTable($data, $handle, $field) {
- $fieldData = array();
-
- // Get the table columns - sent through as fieldname[col]
- preg_match_all('/\w+/', $handle, $matches);
-
- if (isset($matches[0])) {
- $fieldHandle = $matches[0][0];
- $columnHandle = $matches[0][1];
+ if ($field) {
+ $service = $this->_getService($field->type);
- $rows = ArrayHelper::stringToArray($data);
-
- foreach ($rows as $i => $row) {
- if (is_array($row)) {
- foreach ($row as $j => $r) {
- // Check for false for checkbox
- if ($r === 'false') {
- $r = null;
- }
-
- $fieldData[$i+1] = array(
- 'col'.$columnHandle => $r,
- );
- }
- } else {
- // Check for false for checkbox
- if ($row === 'false') {
- $row = null;
- }
-
- $fieldData[$i+1] = array(
- 'col'.$columnHandle => $row,
- );
- }
- }
+ // Get data for the field we're mapping to - can be all sorts of logic here
+ return $service->prepFieldData($element, $field, $data, $handle, $options);
}
- return $fieldData;
+ return $data;
}
- public function prepTags($data, $field) {
- $fieldData = array();
-
- if (!empty($data)) {
- $settings = $field->getFieldType()->getSettings();
-
- // Get tag group id
- $source = $settings->getAttribute('source');
- list($type, $groupId) = explode(':', $source);
-
- $tags = ArrayHelper::stringToArray($data);
-
- foreach ($tags as $tag) {
- $tagArray = array();
-
- if (!empty($tag)) {
-
- // Find existing tag
- $criteria = craft()->elements->getCriteria(ElementType::Tag);
- $criteria->title = DbHelper::escapeParam($tag);
- $criteria->limit = 1;
- $criteria->groupId = $groupId;
-
- if (!$criteria->total()) {
- // Create tag if one doesn't already exist
- $newtag = new TagModel();
- $newtag->getContent()->title = $tag;
- $newtag->groupId = $groupId;
-
- // Save tag
- if (craft()->tags->saveTag($newtag)) {
- $tagArray = array($newtag->id);
- }
- } else {
- $tagArray = $criteria->ids();
- }
- }
+ public function postForFieldType($element, &$data, $handle)
+ {
+ $field = null;
- // Add tags to data array
- $fieldData = array_merge($fieldData, $tagArray);
- }
+ if (isset($this->_fields[$handle])) {
+ $field = $this->_fields[$handle];
}
- return $fieldData;
- }
-
- public function prepUsers($data, $field) {
- $fieldData = array();
-
- if (!empty($data)) {
- $settings = $field->getFieldType()->getSettings();
-
- // Get source id's for connecting
- $groupIds = array();
- $sources = $settings->sources;
- if (is_array($sources)) {
- foreach ($sources as $source) {
- list($type, $id) = explode(':', $source);
- $groupIds[] = $id;
- }
- }
-
- $users = ArrayHelper::stringToArray($data);
-
- foreach ($users as $user) {
- $criteria = craft()->elements->getCriteria(ElementType::User);
- $criteria->groupId = $groupIds;
- $criteria->limit = $settings->limit;
- $criteria->search = $user;
+ if ($field) {
+ $service = $this->_getService($field->type);
- $fieldData = array_merge($fieldData, $criteria->ids());
+ // Get data for the field we're mapping to - can be all sorts of logic here
+ if (method_exists($service, 'postFieldData')) {
+ return $service->postFieldData($element, $field, $data, $handle);
}
}
-
- // Check for field limit - only return the specified amount
- if ($fieldData) {
- if ($field->settings['limit']) {
- $fieldData = array_chunk($fieldData, $field->settings['limit']);
- $fieldData = $fieldData[0];
- }
- }
-
- return $fieldData;
}
-
// Function for third-party plugins to provide custom mapping options for fieldtypes
- public function getCustomOption($fieldHandle)
+ public function getFieldMapping($fieldType)
{
- $options = craft()->plugins->call('registerFeedMeMappingOptions');
-
- foreach ($options as $pluginHandle => $option) {
- if (isset($option[$fieldHandle])) {
- return $option[$fieldHandle];
- }
- }
-
- return false;
+ return $this->_getService($fieldType)->getMappingTemplate();
}
- // Some post-processing needs to be done, specifically for a Matrix field. Unfortuntely, multiple
- // blocks are added out of order, which is messy - fix this here. Fortuntely, we have a 'order' attribute
- // on each block. Also call any third-party post processing (looking at you Super Table).
- public function postForFieldType(&$fieldData, $element)
- {
- // This is less intensive than craft()->fields->getFieldByHandle($fieldHandle);
- /*foreach ($fieldData as $fieldHandle => $data) {
- if (is_array($data)) {
-
- // Check for the order attr, otherwise not what we're after
- if (isset(array_values($data)[0]['order'])) {
- $orderedMatrixData = array();
- $tempMatrixData = array();
- foreach ($data as $key => $subField) {
- $tempMatrixData[$subField['order']][$key] = $subField;
- }
+ // Private Methods
+ // =========================================================================
- $fieldData[$fieldHandle] = array();
+ private function _getService($fieldType)
+ {
+ if (isset($this->_services[$fieldType])) {
+ return $this->_services[$fieldType];
+ }
- foreach ($tempMatrixData as $key => $subField) {
- $fieldData[$fieldHandle] = array_merge($fieldData[$fieldHandle], $subField);
- }
- }
- }
- }*/
+ // Get the service for the Field Type we're dealing with
+ if (!$service = craft()->feedMe->getFieldTypeService($fieldType)) {
+ throw new Exception(Craft::t('Unknown FieldType Service called.'));
+ } else {
+ $this->_services[$fieldType] = $service;
+ }
- // Third-party fieldtype support
- craft()->plugins->call('postForFeedMeFieldType', array(&$fieldData, $element));
+ return $service;
}
-
}
diff --git a/feedme/services/FeedMe_LicenseService.php b/feedme/services/FeedMe_LicenseService.php
new file mode 100644
index 00000000..fb6e1b41
--- /dev/null
+++ b/feedme/services/FeedMe_LicenseService.php
@@ -0,0 +1,188 @@
+plugin = craft()->plugins->getPlugin('feedMe');
+ $this->pluginVersion = $this->plugin->getVersion();
+ $this->licenseKey = $this->getLicenseKey();
+
+ $this->edition = $this->plugin->getSettings()->edition;
+ }
+
+ public function ping()
+ {
+ if (craft()->request->isCpRequest()) {
+ if (!craft()->cache->get($this->pingStateKey)) {
+ $et = new FeedMe_License(static::Ping, $this->pluginHandle, $this->pluginVersion, $this->licenseKey);
+ $etResponse = $et->phoneHome();
+ craft()->cache->set($this->pingStateKey, true, $this->pingCacheTime);
+
+ return $this->_handleEtResponse($etResponse);
+ }
+ }
+
+ return null;
+ }
+
+ public function isProEdition()
+ {
+ if ($this->getEdition() == 1) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public function getEdition()
+ {
+ $edition = 0;
+ if ($this->edition !== null) {
+ if ($this->edition == 1) {
+ $edition = 1;
+ }
+ }
+
+ return $edition;
+ }
+
+ public function setEdition($edition)
+ {
+ $settings = array('edition' => $edition);
+ craft()->plugins->savePluginSettings($this->plugin, $settings);
+ $this->edition = $edition;
+ }
+
+ public function getLicenseKey()
+ {
+ return craft()->plugins->getPluginLicenseKey('FeedMe');
+ }
+
+ public function setLicenseKey($licenseKey)
+ {
+ craft()->plugins->setPluginLicenseKey('FeedMe', $licenseKey);
+ $this->licenseKey = $licenseKey;
+ }
+
+ public function getLicenseKeyStatus()
+ {
+ return craft()->plugins->getPluginLicenseKeyStatus('FeedMe');
+ }
+
+ public function setLicenseKeyStatus($licenseKeyStatus)
+ {
+ craft()->plugins->setPluginLicenseKeyStatus('FeedMe', $licenseKeyStatus);
+ }
+
+ public function getLicenseInfo()
+ {
+ $et = new FeedMe_License(static::GetLicenseInfo, $this->pluginHandle, $this->pluginVersion, $this->licenseKey);
+ $etResponse = $et->phoneHome(true);
+
+ return $this->_handleEtResponse($etResponse);
+ }
+
+ public function decodeEtModel($attributes)
+ {
+ if ($attributes) {
+ $attributes = JsonHelper::decode($attributes);
+
+ if (is_array($attributes)) {
+ $etModel = new FeedMe_LicenseModel($attributes);
+
+ // Make sure it's valid. (At a minimum, localBuild and localVersion should be set.)
+ if ($etModel->validate()) {
+ return $etModel;
+ }
+ }
+ }
+ return null;
+ }
+
+ public function unregisterLicenseKey()
+ {
+ $et = new FeedMe_License(static::UnregisterPlugin, $this->pluginHandle, $this->pluginVersion, $this->licenseKey);
+ $etResponse = $et->phoneHome(true);
+
+ $this->setLicenseKey(null);
+ $this->setLicenseKeyStatus(LicenseKeyStatus::Unknown);
+ $this->setEdition('0');
+
+ return $etResponse;
+ }
+
+ public function transferLicenseKey()
+ {
+ $et = new FeedMe_License(static::TransferPlugin, $this->pluginHandle, $this->pluginVersion, $this->licenseKey);
+ $etResponse = $et->phoneHome(true);
+
+ return $etResponse;
+ }
+
+ public function registerPlugin($licenseKey)
+ {
+ $et = new FeedMe_License(static::RegisterPlugin, $this->pluginHandle, $this->pluginVersion, $licenseKey);
+ $etResponse = $et->phoneHome(true);
+
+ return $this->_handleEtResponse($etResponse);
+ }
+
+
+
+ // Private Methods
+ // =========================================================================
+
+ private function _handleEtResponse($etResponse)
+ {
+ if (!empty($etResponse->data['success'])) {
+ // Set the local details
+ $this->setEdition('1');
+ $this->setLicenseKeyStatus(LicenseKeyStatus::Valid);
+ return true;
+ } else {
+ $this->setEdition('0');
+
+ if (!empty($etResponse->errors)) {
+ switch ($etResponse->errors[0]) {
+ case 'nonexistent_plugin_license':
+ $this->setLicenseKeyStatus(LicenseKeyStatus::Invalid);
+ break;
+ case 'plugin_license_in_use':
+ $this->setLicenseKeyStatus(LicenseKeyStatus::Mismatched);
+ break;
+ default:
+ $this->setLicenseKeyStatus(LicenseKeyStatus::Unknown);
+ }
+
+ FeedMePlugin::log('License error: ' . $etResponse->errors[0], LogLevel::Error, true);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/feedme/services/FeedMe_LogsService.php b/feedme/services/FeedMe_LogsService.php
deleted file mode 100644
index 045dfab2..00000000
--- a/feedme/services/FeedMe_LogsService.php
+++ /dev/null
@@ -1,70 +0,0 @@
-order = 'id desc';
-
- return FeedMe_LogsRecord::model()->findAll($criteria);
- }
-
- public function showLog($logs)
- {
- $criteria = new \CDbCriteria();
- $criteria->condition = 'logsId = :logs_id';
- $criteria->params = array(
- ':logs_id' => $logs,
- );
-
- $logItems = FeedMe_LogRecord::model()->findAll($criteria);
-
- return $logItems;
- }
-
- public function start($settings)
- {
- $logs = new FeedMe_LogsRecord();
- $logs->feedId = $settings['feed']->id;
- $logs->items = $settings['items'];
-
- $logs->save(false);
-
- return $logs->id;
- }
-
- public function log($settings, $errors, $level)
- {
- // Firstly, store in plugin log file (use $level to control log level)
- FeedMePlugin::log(print_r($errors, true), $level, true);
-
- // Save this log to the DB as well
- if (isset($settings->attributes['logsId'])) {
- $logsId = $settings->logsId;
-
- if (FeedMe_LogsRecord::model()->findById($logsId)) {
- $log = new FeedMe_LogRecord();
- $log->logsId = $logsId;
- $log->errors = print_r($errors, true);
-
- $log->save(false);
- }
- }
- }
-
- public function end($settings)
- {
- if (isset($settings->attributes['logsId'])) {
- $logsId = $settings->logsId;
-
- $logs = FeedMe_LogsRecord::model()->findById($logsId);
-
- $logs->save(false);
- }
- }
-}
diff --git a/feedme/services/FeedMe_ProcessService.php b/feedme/services/FeedMe_ProcessService.php
new file mode 100644
index 00000000..5c447235
--- /dev/null
+++ b/feedme/services/FeedMe_ProcessService.php
@@ -0,0 +1,462 @@
+feedMe_license->isProEdition()) {
+ throw new Exception(Craft::t('Feed Me is not licensed.'));
+ }
+ }
+
+ $return = $feed->attributes;
+
+ // Set our start time to track feed processing time
+ $this->_time_start = microtime(true);
+
+ // Add some additional information to our FeedModel - for ease of use in processing
+ $return['fields'] = array();
+ $return['existingElements'] = array();
+
+ if (!$feed['fieldMapping']) {
+ throw new Exception(Craft::t('Field mapping not setup.'));
+ }
+
+ // Start looping through all the mapped fields - checking for nested nodes
+ foreach ($feed['fieldMapping'] as $itemNode => $destination) {
+ // Forget about any fields mapped as not to import
+ if ($destination != 'noimport') {
+ $return['fields'][$itemNode] = $destination;
+ }
+ }
+
+ // Get the service for the Element Type we're dealing with
+ if (!($this->_service = craft()->feedMe->getElementTypeService($feed['elementType']))) {
+ throw new Exception(Craft::t('Unknown Element Type Service called.'));
+ }
+
+ // If our duplication handling is to delete - we delete all elements
+ if (FeedMeDuplicate::isDelete($feed)) {
+ $criteria = $this->_service->setCriteria($feed);
+
+ $return['existingElements'] = $criteria->ids();
+ }
+
+ // Setup a bunch of variables that can be done once-off at the start of feed processing
+ // rather than on each step. This is done for max performance - even a little
+ $this->_criteria = $this->_service->setCriteria($feed);
+
+ // Our main data-parsing function. Handles the actual data values, defaults and field options
+ foreach ($feedData as $key => $nodeData) {
+ $this->_data[$key] = $this->_prepFieldData($return['fields'], $nodeData, $feed['fieldDefaults']);
+ }
+
+ return $return;
+ }
+
+ public function processFeed($step, $feed)
+ {
+ $existingElement = false;
+ $fieldData = array();
+ $uniqueMatches = array();
+
+ // We can opt-out of updating certain elements if a field is switched on
+ $skipUpdateFieldHandle = craft()->config->get('skipUpdateFieldHandle', 'feedMe');
+
+ //
+ // Lets get started!
+ //
+
+ // Set up a model for this Element Type
+ $element = $this->_service->setModel($feed);
+
+ // Set criteria according to Element Type
+ $criteria = $this->_criteria;
+
+ // From the raw data in our feed, process it ready for mapping (more to do below)
+ $data = $this->_data[$step];
+
+ // For each chunck of import-ready data, we need to further prepare it for Craft
+ foreach ($data as $handle => $preppedData) {
+ // From each field, we may need to process the raw data for Craft fields
+ $fieldData[$handle] = craft()->feedMe_fields->prepForFieldType($element, $preppedData, $handle);
+ }
+
+ //
+ // Check for Add/Update/Delete for existing elements
+ //
+
+ // Check to see if an element already exists
+ $existingElements = $this->_service->matchExistingElement($criteria, $fieldData, $feed);
+
+ if (isset($existingElements[0])) {
+ $existingElement = $existingElements[0];
+ }
+
+ // If there's an existing matching element
+ if ($existingElement) {
+
+ // If we're deleting or updating an existing element, we want to focus on that one
+ if (FeedMeDuplicate::isUpdate($feed)) {
+ $element = $existingElement;
+ }
+
+ // There's also a config settings for a field to opt-out of updating. Check against that
+ if ($skipUpdateFieldHandle) {
+ $updateField = $element->content->getAttribute($skipUpdateFieldHandle);
+
+ // We've got our special field on this element, and its switched on
+ if ($updateField === '1') {
+ return;
+ }
+ }
+
+ // If we're adding only, and there's an existing element - quit now
+ if (FeedMeDuplicate::isAdd($feed, true)) {
+ return;
+ }
+ } else {
+ // Have we set to update-only? There are no existing elements, so skip
+ if (FeedMeDuplicate::isUpdate($feed, true)) {
+ return;
+ }
+ }
+
+ // Prepare Element Type model - this sets all Element Type attributes (Title, slug, etc).
+ $element = $this->_service->prepForElementModel($element, $fieldData, $feed);
+
+ // Allow field types to modify content once an element has been properly setup and identified
+ foreach ($data as $handle => $preppedData) {
+ craft()->feedMe_fields->postForFieldType($element, $fieldData, $handle, $handle);
+ }
+
+ // Set the Element Type's fields data - but only if we're not targeting a locale
+ if (!$feed['locale']) {
+ $element->setContentFromPost($fieldData);
+ }
+
+ $this->_debugOutput($element->attributes);
+ $this->_debugOutput($fieldData);
+
+ // Save the element
+ if ($this->_service->save($element, $fieldData, $feed)) {
+ // Give elements a chance to perform actions after save
+ $this->_service->afterSave($element, $fieldData, $feed);
+
+ if ($existingElement) {
+ FeedMePlugin::log($feed['name'] . ': ' . $feed['elementType'] . ' ' . $element->id . ' updated.', LogLevel::Info, true);
+ } else {
+ FeedMePlugin::log($feed['name'] . ': ' . $feed['elementType'] . ' ' . $element->id . ' added.', LogLevel::Info, true);
+ }
+
+ // Store our successfully processed element for feedback in logs, but also in case we're deleting
+ $this->_processedElements[] = $element->id;
+ } else {
+ if ($element->getErrors()) {
+ throw new Exception(json_encode($element->getErrors()));
+ } else {
+ throw new Exception(Craft::t('Unknown Element saving error occurred.'));
+ }
+ }
+ }
+
+ public function finalizeAfterProcess($settings, $feed)
+ {
+ if (FeedMeDuplicate::isDelete($feed)) {
+ $deleteIds = array_diff($settings['existingElements'], $this->_processedElements);
+ $criteria = $this->_service->setCriteria($feed);
+ $criteria->id = $deleteIds;
+ $elementsToDelete = $criteria->find();
+
+ if ($elementsToDelete) {
+ if ($this->_service->delete($elementsToDelete)) {
+ FeedMePlugin::log($feed->name . ': The following elements have been deleted: ' . print_r($deleteIds, true) . '.', LogLevel::Info, true);
+ } else {
+ if ($element->getErrors()) {
+ throw new Exception(json_encode($element->getErrors()));
+ } else {
+ throw new Exception(Craft::t('Something went wrong while deleting elements.'));
+ }
+ }
+ }
+ }
+
+ // Log the total time taken to process the feed
+ $time_end = microtime(true);
+ $execution_time = number_format(($time_end - $this->_time_start), 2);
+ FeedMePlugin::log($feed->name . ': Processing ' . count($this->_processedElements) . ' elements finished in ' . $execution_time . 's', LogLevel::Info, true);
+
+ $this->_debugOutput('Processing ' . count($this->_processedElements) . ' elements finished in ' . $execution_time . 's.');
+ }
+
+ public function debugFeed($feedId, $limit)
+ {
+ $this->_debug = true;
+
+ $feed = craft()->feedMe_feeds->getFeedById($feedId);
+
+ $feedData = craft()->feedMe_data->getFeed($feed->feedType, $feed->feedUrl, $feed->primaryElement, $feed);
+ $feedSettings = craft()->feedMe_process->setupForProcess($feed, $feedData);
+
+ // Do we even have any data to process?
+ if (!count($feedData)) {
+ $this->_debugOutput('No feed items to process.');
+ return true;
+ }
+
+ foreach ($feedData as $key => $data) {
+ craft()->feedMe_process->processFeed($key, $feedSettings);
+
+ if ($key === ($limit - 1)) {
+ break;
+ }
+ }
+
+ craft()->feedMe_process->finalizeAfterProcess($feedSettings, $feed);
+ }
+
+
+
+ // Event Handlers
+ // =========================================================================
+
+ public function onBeforeProcessFeed(\CEvent $event)
+ {
+ $this->raiseEvent('onBeforeProcessFeed', $event);
+ }
+
+ public function onStepProcessFeed(\CEvent $event)
+ {
+ $this->raiseEvent('onStepProcessFeed', $event);
+ }
+
+ public function onProcessFeed(\CEvent $event)
+ {
+ $this->raiseEvent('onProcessFeed', $event);
+ }
+
+
+
+ // Private Methods
+ // =========================================================================
+
+ private function _prepFieldData($fieldMapping, $feedData, $fieldDefaults)
+ {
+ $parsedData = array();
+
+ // First, loop through all the field defaults. Important to do first, as if we set a default
+ // but aren't actually mapping it to anything, we'll never enter the below loop
+ if (is_array($fieldDefaults)){
+ foreach ($fieldDefaults as $fieldHandle => $feedHandle) {
+ if (isset($feedHandle) && $feedHandle !== '') {
+ $parsedData[$fieldHandle]['data'] = $feedHandle;
+ }
+ }
+ }
+
+ foreach ($fieldMapping as $fieldHandle => $feedHandle) {
+ if ($feedHandle == 'noimport') {
+ continue;
+ }
+
+ // We display and store field mapping with '/' and '/.../', for the users benefit,
+ // but Extract needs them as '.' or '{*}', so we convert them here.
+ // Turns 'my/repeating/.../field' into 'my.repeating.*.field'
+ $extractFeedHandle = str_replace('/.../', '.*.', $feedHandle);
+ $extractFeedHandle = str_replace('[]', '.*', $extractFeedHandle);
+ $extractFeedHandle = str_replace('/', '.', $extractFeedHandle);
+
+ // Have a default ready to go in case a value can't be found in the feed
+ $defaultValue = isset($fieldDefaults[$fieldHandle]) ? $fieldDefaults[$fieldHandle] : null;
+
+ // Get our value from the feed
+ $value = FeedMeArrayHelper::arrayGet($feedData, $extractFeedHandle, $defaultValue);
+
+ // Store it in our data array, with the Craft field handle we're mapping to
+ if (isset($value) && $value !== null) {
+ if (is_array($value)) {
+ // Our arrayGet() function keeps empty indexes, which is super-important
+ // for Matrix. Here, this filters them out, while keeping the indexes intact
+ $value = Hash::filter($value);
+ }
+
+ // If its an empty string, and there's already a default value, use that
+ if ($value === '' && isset($parsedData[$fieldHandle]['data'])) {
+ $parsedData[$fieldHandle]['data'] = $parsedData[$fieldHandle]['data'];
+ } else {
+ $parsedData[$fieldHandle]['data'] = $value;
+ }
+ }
+
+ // An annoying check for inconsistent nodes - I'm looking at you XML
+ if (strstr($extractFeedHandle, '.*.')) {
+ // Check for any single data. While we expect something like: [Assets/Asset/.../Img] => image_1.jpg
+ // We often get data that can be mapped as: [Assets/Asset/Img] => image_3.jpg
+ // So we check for both...
+ $testSingleFeedHandle = str_replace('.*.', '.', $extractFeedHandle);
+ $value = FeedMeArrayHelper::arrayGet($feedData, $testSingleFeedHandle, $defaultValue);
+
+ if (isset($value) && $value !== '') {
+ $parsedData[$fieldHandle]['data'] = $value;
+ }
+ }
+
+ // If this field has any options, add those to the above node, rather than separate
+ if (strstr($fieldHandle, '-options-')) {
+
+ $fieldHandles = FeedMeArrayHelper::multiExplode(array('--', '-'), $fieldHandle);
+
+ if (strstr($fieldHandle, '--')) {
+ array_splice($fieldHandles, 1, 0, 'data');
+ }
+
+ // Check if we've got data for this field, otherwise its unnessesarry
+ if (isset($parsedData[$fieldHandles[0]]['data'])) {
+ FeedMeArrayHelper::arraySet($parsedData, $fieldHandles, $feedHandle);
+ }
+
+ unset($parsedData[$fieldHandle]); // Remove un-needed original
+
+ } else if (strstr($fieldHandle, '-fields-')) {
+
+ // If this field has any nested fields, add those to the above node, rather than separate
+ // Note this has to be recursive as there is field-mapping for these inner fields.
+ $fieldHandles = FeedMeArrayHelper::multiExplode(array('--', '-'), $fieldHandle);
+
+ if (strstr($fieldHandle, '--')) {
+ array_splice($fieldHandles, 1, 0, 'data');
+ }
+
+ $nestedData = $this->_getInnerFieldData($feedData, $feedHandle);
+
+ // Check if we've got data for this field, otherwise its unnessesarry
+ if (isset($parsedData[$fieldHandles[0]]['data'])) {
+ FeedMeArrayHelper::arraySet($parsedData, $fieldHandles, $nestedData);
+ }
+
+ unset($parsedData[$fieldHandle]); // Remove un-needed original
+
+ } else if (strstr($fieldHandle, '--')) {
+ // Some fields like a Table contain multiple blocks of data, each needing to be mapped individually
+ // which means feed-mapping will give us something like below. We need to re-jig things.
+ // [table--col1] => Array (
+ // [0] => Option1
+ // [1] => Option3
+ // )
+ // [table--col2] => Array (
+ // [0] => Option2
+ // [1] => Option4
+ // )
+
+ $split = explode('--', $fieldHandle);
+ array_splice($split, 1, 0, 'data');
+
+ $nestedData = $this->_getInnerFieldData($feedData, $feedHandle);
+
+ FeedMeArrayHelper::arraySet($parsedData, $split, $nestedData);
+ unset($parsedData[$fieldHandle]); // Remove un-needed original
+ }
+ }
+
+ //$this->_debugOutput($parsedData);
+
+ return $parsedData;
+ }
+
+ // A more lightweight version of our main feed-data-getting function
+ // I suppose this could be recursive, but lets not make life harder than it already is...
+ private function _getInnerFieldData($feedData, $feedHandle)
+ {
+ $parsedData = array();
+
+ // We display and store field mapping with '/' and '/.../', for the users benefit,
+ // but Extract needs them as '.' or '{*}', so we convert them here.
+ // Turns 'my/repeating/.../field' into 'my.repeating.*.field'
+ $extractFeedHandle = str_replace('/.../', '.*.', $feedHandle);
+ $extractFeedHandle = str_replace('[]', '.*', $extractFeedHandle);
+ $extractFeedHandle = str_replace('/', '.', $extractFeedHandle);
+
+ // Use Extract to pull out our nested data. Super-cool!
+ $value = FeedMeArrayHelper::arrayGet($feedData, $extractFeedHandle);
+
+ // An annoying check for inconsistent nodes - I'm looking at you XML
+ if (strstr($extractFeedHandle, '.*.')) {
+ // Check for any single data. While we expect something like: [Assets/Asset/.../Img] => image_1.jpg
+ // We often get data that can be mapped as: [Assets/Asset/Img] => image_3.jpg
+ // So we check for both...
+ //$testSingleFeedHandle = $this->str_lreplace('.*.', '.', $extractFeedHandle);
+ $testSingleFeedHandle = $this->str_lreplace('.*.', '.', $extractFeedHandle);
+ $tempValue = FeedMeArrayHelper::arrayGet($feedData, $testSingleFeedHandle);
+
+ // Check for array of nulls
+ if (is_array($tempValue)) {
+ if (count(array_filter($tempValue)) === 0) {
+ $tempValue = '';
+ }
+ }
+
+ if (isset($tempValue) && $tempValue !== null) {
+ $value = $tempValue;
+ }
+ }
+
+ // Store it in our data array, with the Craft field handle we're mapping to
+ if (isset($value) && $value !== '') {
+ if (is_array($value)) {
+ $value = Hash::filter($value);
+ }
+
+ //if (strstr($feedHandle, '[]')) {
+ //$parsedData['data'] = array($value);
+ //} else {
+ $parsedData['data'] = $value;
+ //}
+ }
+
+ return $parsedData;
+ }
+
+ private function str_lreplace($search, $replace, $subject)
+ {
+ $pos = strrpos($subject, $search);
+
+ if ($pos !== false) {
+ $subject = substr_replace($subject, $replace, $pos, strlen($search));
+ }
+
+ return $subject;
+ }
+
+ private function _debugOutput($data)
+ {
+ if ($this->_debug) {
+ echo '
';
+ print_r($data);
+ echo '
';
+ }
+ }
+}
diff --git a/feedme/tasks/FeedMeTask.php b/feedme/tasks/FeedMeTask.php
index 5cdec486..2e9343e8 100644
--- a/feedme/tasks/FeedMeTask.php
+++ b/feedme/tasks/FeedMeTask.php
@@ -7,12 +7,10 @@ class FeedMeTask extends BaseTask
// =========================================================================
private $_feed;
- private $_logsId;
private $_feedData;
private $_feedSettings;
- private $_backup;
- private $_chunkedFeedData;
- private $_processedEntries = array();
+ private $_errors;
+ private $_totalSteps;
// Public Methods
// =========================================================================
@@ -24,53 +22,79 @@ public function getDescription()
public function getTotalSteps()
{
- // Get settings
- $settings = $this->getSettings();
+ try {
+ // Get settings
+ $settings = $this->getSettings();
- // Get the Feed
- $this->_feed = $settings->feed;
+ // Get the Feed
+ $this->_feed = $settings->feed;
- // There are also a few once-off things we can do for this feed to assist with processing.
- $this->_feedSettings = craft()->feedMe->setupForImport($this->_feed);
+ // Get the data for the mapping screen, based on the URL provided
+ $this->_feedData = craft()->feedMe_data->getFeed($this->_feed->feedType, $this->_feed->feedUrl, $this->_feed->primaryElement, $this->_feed);
- // Get the data for the mapping screen, based on the URL provided
- $this->_feedData = craft()->feedMe_feed->getFeed($this->_feed->feedType, $this->_feed->feedUrl, $this->_feed->primaryElement);
+ // There are also a few once-off things we can do for this feed to assist with processing.
+ $this->_feedSettings = craft()->feedMe_process->setupForProcess($this->_feed, $this->_feedData);
- if (!$this->_feedData) {
- FeedMePlugin::log($this->_feed->name . ': FeedMeError', LogLevel::Error, true);
- return false;
- }
-
- // Chunk the feed data into chunks of 100 - optimises mapping process by not calling service each step
- $this->_chunkedFeedData = array_chunk($this->_feedData, 100);
+ // Store for performance
+ $this->_totalSteps = count($this->_feedData);
- // Delete all the entry caches
- craft()->templateCache->deleteCachesByElementType('Entry');
+ } catch (\Exception $e) {
+ FeedMePlugin::log($this->_feed->name . ': ' . $e->getMessage(), LogLevel::Error, true);
- // Create a backup before we do anything to the DB
- if ($this->_feed->backup) {
- $backup = craft()->db->backup();
+ $this->_totalSteps = 0;
}
// Take a step for every row
- return count($this->_chunkedFeedData);
+ return $this->_totalSteps;
}
public function runStep($step)
{
- $result = craft()->feedMe->importNode($this->_chunkedFeedData[$step], $this->_feed, $this->_feedSettings);
- $this->_processedEntries = array_merge($this->_processedEntries, $result['processedEntries']);
+ // Do we even have any data to process?
+ if (!$this->_totalSteps) {
+ return true;
+ }
- // For delete, at the end of our processing, we delete all entries not recorded
- if ($step == $this->getTotalSteps()-1) {
- craft()->feedMe->deleteLeftoverEntries($this->_feedSettings, $this->_feed, $this->_processedEntries, $result);
+ try {
+ // On the first run of the feed
+ if (!$step) {
+ // Fire an "onBeforeProcessFeed" event
+ $event = new Event($this, array('settings' => $this->_feedSettings));
+ craft()->feedMe_process->onBeforeProcessFeed($event);
+ }
+
+ // Process each feed node
+ if (isset($this->_feedData[$step])) {
+ craft()->feedMe_process->processFeed($step, $this->_feedSettings);
+ } else {
+ FeedMePlugin::log($this->_feed->name . ': FeedMeError', LogLevel::Error, true);
+ }
+
+ // When finished
+ if ($step == ($this->_totalSteps - 1)) {
+ craft()->feedMe_process->finalizeAfterProcess($this->_feedSettings, $this->_feed);
+
+ // Fire an "onProcessFeed" event
+ $event = new Event($this, array('settings' => $this->_feedSettings));
+ craft()->feedMe_process->onProcessFeed($event);
+ }
+ } catch (\Exception $e) {
+ FeedMePlugin::log($this->_feed->name . ': ' . $e->getMessage(), LogLevel::Error, true);
+
+ // Keep track of errors for the last - shown at final step
+ $this->_errors[] = $this->_feed->name . ': ' . $e->getMessage();
+
+ // Act cool for now - try to process other items
+ //return false;
}
- if (!$result['result']) {
- return 'Feed Me Failure: Check Feed Me logs.';
- } else {
- return true;
+ if ($step == ($this->_totalSteps - 1)) {
+ if (count($this->_errors) > 0) {
+ return false;
+ }
}
+
+ return true;
}
// Protected Methods
diff --git a/feedme/templates/_includes/elements/asset/column.html b/feedme/templates/_includes/elements/asset/column.html
new file mode 100644
index 00000000..22998005
--- /dev/null
+++ b/feedme/templates/_includes/elements/asset/column.html
@@ -0,0 +1,3 @@
+{% set group = craft.feedMe.getAssetSourceById(feed.elementGroup[elementType]) %}
+
+{{ group.name ?? '' }}
\ No newline at end of file
diff --git a/feedme/templates/_includes/elements/asset/groups.html b/feedme/templates/_includes/elements/asset/groups.html
new file mode 100644
index 00000000..a63232eb
--- /dev/null
+++ b/feedme/templates/_includes/elements/asset/groups.html
@@ -0,0 +1,11 @@
+{% set assets = craft.feedMe.getElementTypeGroups(elementType) %}
+
+{{ forms.selectField({
+ label: "Asset Source" | t,
+ instructions: 'Choose the asset source you want to save your feed data into.' | t,
+ id: 'elementGroup-' ~ elementType,
+ name: 'elementGroup[' ~ elementType ~ ']',
+ options: craft.feedme.getSelectOptions(assets),
+ value: feed.elementGroup[elementType] ?? '',
+ required: true,
+}) }}
diff --git a/feedme/templates/_includes/elements/asset/map.html b/feedme/templates/_includes/elements/asset/map.html
new file mode 100644
index 00000000..eeb4d97e
--- /dev/null
+++ b/feedme/templates/_includes/elements/asset/map.html
@@ -0,0 +1,89 @@
+{% if feed.elementGroup %}
+ {% set sourceId = feed.elementGroup[elementType] %}
+
+ {% set source = craft.feedMe.getAssetSourceById(sourceId) %}
+{% endif %}
+
+{% set fields = [{
+ label: 'Title' | t,
+ handle: 'title',
+ default: forms.textField({
+ id: 'fieldDefaults-title',
+ name: 'fieldDefaults[title]',
+ value: feed.fieldDefaults.title ?? '',
+ }),
+}, {
+ label: 'Filename' | t,
+ handle: 'filename',
+ instructions: 'The filename for this asset (not including the path).' | t,
+ required: true,
+}, {
+ label: 'Asset ID' | t,
+ handle: 'id',
+ instructions: 'Warning: This should only be used for an existing Craft Asset ID.' | t,
+}] %}
+
+
{{ 'Asset Fields' | t }}
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for field in fields %}
+ {% if field.handle == 'filename' %}
+ {{ feedMeMacro.generateAssetRow(_context, field) }}
+ {% else %}
+ {{ feedMeMacro.generateRow(_context, field) }}
+ {% endif %}
+ {% endfor %}
+
+
+
+{% for tab in craft.fields.getLayoutById(source.fieldLayoutId).getTabs() %}
+
+
+
{{ tab.name }} Fields
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set variables = { field: field, fieldtype: fieldtype, feed: feed, feedData: feedData } %}
+ {% include 'feedme/_includes/field' with variables %}
+ {% endfor %}
+
+
+{% endfor %}
+
+
+
+
{{ "Set a unique identifier for existing elements" | t }}
+
+
{{ "Select the fields you want to use to check for existing elements. When selected, Feed Me will look for existing elements that match the fields provided below and either update, or skip depending on your choice of Import Strategy." | t }}
+
+{% for tab in craft.fields.getLayoutById(source.fieldLayoutId).getTabs() %}
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set fields = fields | merge([{ label: field.name, handle: field.handle }]) %}
+ {% endfor %}
+{% endfor %}
+
+
diff --git a/feedme/templates/_includes/elements/category/column.html b/feedme/templates/_includes/elements/category/column.html
new file mode 100644
index 00000000..8777452e
--- /dev/null
+++ b/feedme/templates/_includes/elements/category/column.html
@@ -0,0 +1,3 @@
+{% set group = craft.categoryGroups.getGroupById(feed.elementGroup[elementType]) %}
+
+{{ group.name ?? '' }}
\ No newline at end of file
diff --git a/feedme/templates/_includes/elements/category/groups.html b/feedme/templates/_includes/elements/category/groups.html
new file mode 100644
index 00000000..982b021a
--- /dev/null
+++ b/feedme/templates/_includes/elements/category/groups.html
@@ -0,0 +1,11 @@
+{% set categories = craft.feedMe.getElementTypeGroups(elementType) %}
+
+{{ forms.selectField({
+ label: "Category Group" | t,
+ instructions: 'Choose the category group you want to save your feed data into.' | t,
+ id: 'elementGroup-' ~ elementType,
+ name: 'elementGroup[' ~ elementType ~ ']',
+ options: craft.feedme.getSelectOptions(categories),
+ value: feed.elementGroup[elementType] ?? '',
+ required: true,
+}) }}
diff --git a/feedme/templates/_includes/elements/category/map.html b/feedme/templates/_includes/elements/category/map.html
new file mode 100644
index 00000000..36b5547a
--- /dev/null
+++ b/feedme/templates/_includes/elements/category/map.html
@@ -0,0 +1,117 @@
+{% if feed.elementGroup %}
+ {% set groupId = feed.elementGroup[elementType] %}
+
+ {% set group = craft.categoryGroups.getGroupById(groupId) %}
+{% endif %}
+
+{% set categories = [{ label: 'Don\'t import', value: '' }] %}
+{% for category in craft.categories %}
+ {% set categories = categories | merge([{ label: category.title | slice(0, 40), value: category.id }]) %}
+{% endfor %}
+
+{% set fields = [{
+ label: 'Title' | t,
+ handle: 'title',
+ default: forms.textField({
+ id: 'fieldDefaults-title',
+ name: 'fieldDefaults[title]',
+ value: feed.fieldDefaults.title ?? '',
+ }),
+}, {
+ label: 'Slug' | t,
+ handle: 'slug',
+ instructions: 'If not set, the Slug will be automatically created from Title.' | t,
+}, {
+ label: 'Parent' | t,
+ handle: 'parent',
+ instructions: 'Select a parent category to import these categories under.' | t,
+ default: forms.selectField({
+ id: 'fieldDefaults-parent',
+ name: 'fieldDefaults[parent]',
+ value: feed.fieldDefaults.parent ?? '',
+ options: categories,
+ }),
+}, {
+ label: 'Status' | t,
+ handle: 'enabled',
+ instructions: 'Choose either a default status from the list or the imported field that will contain the status.' | t,
+ default: forms.selectField({
+ id: 'fieldDefaults-enabled',
+ name: 'fieldDefaults[enabled]',
+ value: feed.fieldDefaults.enabled is defined ? feed.fieldDefaults.enabled : '',
+ options: [
+ { label: 'Don\'t import', value: '' },
+ { label: 'Enabled', value: '1' },
+ { label: 'Disabled', value: '0' },
+ ],
+ }),
+}, {
+ label: 'Category ID' | t,
+ handle: 'id',
+ instructions: 'Warning: This should only be used for an existing Craft Category ID.' | t,
+}] %}
+
+
{{ 'Category Fields' | t }}
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for field in fields %}
+ {% if field.handle == 'parent' %}
+ {{ feedMeMacro.generateElementRow(_context, field) }}
+ {% else %}
+ {{ feedMeMacro.generateRow(_context, field) }}
+ {% endif %}
+ {% endfor %}
+
+
+
+{% for tab in craft.fields.getLayoutById(group.fieldLayoutId).getTabs() %}
+
+
+
{{ tab.name }} Fields
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set variables = { field: field, fieldtype: fieldtype, feed: feed, feedData: feedData } %}
+ {% include 'feedme/_includes/field' with variables %}
+ {% endfor %}
+
+
+{% endfor %}
+
+
+
+
{{ "Set a unique identifier for existing elements" | t }}
+
+
{{ "Select the fields you want to use to check for existing elements. When selected, Feed Me will look for existing elements that match the fields provided below and either update, or skip depending on your choice of Import Strategy." | t }}
+
+{% for tab in craft.fields.getLayoutById(group.fieldLayoutId).getTabs() %}
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set fields = fields | merge([{ label: field.name, handle: field.handle }]) %}
+ {% endfor %}
+{% endfor %}
+
+
+
+
+ {% for field in fields %}
+ {{ feedMeMacro.generateRow(_context, field) }}
+ {% endfor %}
+
+
+
+
+
+
{{ 'Product Variant Fields' | t }}
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for field in fieldsVariants %}
+ {{ feedMeMacro.generateRow(_context, field) }}
+ {% endfor %}
+
+
+
+{% for tab in craft.fields.getLayoutById(productType.fieldLayoutId).getTabs() %}
+
+
+
{{ tab.name }} Fields
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set variables = { field: field, fieldtype: fieldtype, feed: feed, feedData: feedData } %}
+ {% include 'feedme/_includes/field' with variables %}
+ {% endfor %}
+
+
+{% endfor %}
+
+{% for tab in craft.fields.getLayoutById(productType.variantFieldLayoutId).getTabs() %}
+
+
+
{{ "Variant Fields" | t }}
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set variables = { field: field, fieldtype: fieldtype, feed: feed, feedData: feedData, handlePrefix: 'variants--' } %}
+ {% include 'feedme/_includes/field' with variables %}
+ {% endfor %}
+
+
+{% endfor %}
+
+
+
+
{{ "Set a unique identifier for existing elements" | t }}
+
+
{{ "Select the fields you want to use to check for existing elements. When selected, Feed Me will look for existing elements that match the fields provided below and either update, or skip depending on your choice of Import Strategy." | t }}
+
+{% set uniqueFields = fields %}
+
+{% for field in fieldsVariants %}
+ {# Special-case for a few attributes - we need to select the default #}
+ {% set handle = field.handle | replace('variant-', '') %}
+
+ {% set uniqueFields = uniqueFields | merge([{ label: 'Variant ' ~ field.label, handle: field.name }]) %}
+{% endfor %}
+
+{% for tab in craft.fields.getLayoutById(productType.fieldLayoutId).getTabs() %}
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set uniqueFields = uniqueFields | merge([{ label: field.name, handle: field.handle }]) %}
+ {% endfor %}
+{% endfor %}
+
+{% for tab in craft.fields.getLayoutById(productType.variantFieldLayoutId).getTabs() %}
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set uniqueFields = uniqueFields | merge([{ label: field.name, handle: 'variants--' ~ field.handle }]) %}
+ {% endfor %}
+{% endfor %}
+
+
diff --git a/feedme/templates/_includes/elements/entry/column.html b/feedme/templates/_includes/elements/entry/column.html
new file mode 100644
index 00000000..0738b377
--- /dev/null
+++ b/feedme/templates/_includes/elements/entry/column.html
@@ -0,0 +1,12 @@
+{% set sectionId = feed.elementGroup[elementType].section %}
+{% set entryTypeId = feed.elementGroup[elementType].entryType %}
+
+{% if sectionId and entryTypeId %}
+ {% set section = craft.sections.getSectionById(sectionId) %}
+ {% set entryType = craft.feedMe.getEntryTypeById(entryTypeId) %}
+
+ {% if section and entryType %}
+ {{ section.name }}
+ {{ entryType.name }}
+ {% endif %}
+{% endif %}
diff --git a/feedme/templates/_includes/elements/entry/groups.html b/feedme/templates/_includes/elements/entry/groups.html
new file mode 100644
index 00000000..4fe62e19
--- /dev/null
+++ b/feedme/templates/_includes/elements/entry/groups.html
@@ -0,0 +1,52 @@
+{% set sections = craft.feedMe.getElementTypeGroups(elementType) %}
+
+{% set sectionEntryTypes = [] %}
+
+{# Create a section-indexed array of element types #}
+{% set entryTypes = [] %}
+{% for section in sections %}
+ {% set options = craft.feedme.getSelectOptions(section.getEntryTypes()) %}
+
+ {# We have to prefix the index, otherwise Twig doesn't maintain numbered index correctly #}
+ {% set entryTypes = entryTypes | merge({ ('item_' ~ section.id): options }) %}
+{% endfor %}
+
+{# Load saved values for feed #}
+{% if feed.elementGroup %}
+ {% set sectionId = feed.elementGroup[elementType].section %}
+ {% set entryTypeId = feed.elementGroup[elementType].entryType %}
+{% endif %}
+
+{% if sectionId is defined and sectionId %}
+ {% set section = craft.sections.getSectionById(sectionId) %}
+
+ {% if section %}
+ {% set sectionEntryTypes = section.getEntryTypes() %}
+ {% endif %}
+{% else %}
+ {% set sectionEntryTypes = sections[0].getEntryTypes() %}
+{% endif %}
+
+
+ {{ forms.selectField({
+ label: "Section" | t,
+ instructions: 'Choose the Section you want to save your feed data to.' | t,
+ class: 'element-parent-group',
+ id: 'elementGroup-' ~ elementType ~ '-section',
+ name: 'elementGroup[' ~ elementType ~ '][section]',
+ options: craft.feedme.getSelectOptions(sections),
+ value: sectionId ?? '',
+ required: true,
+ }) }}
+
+ {{ forms.selectField({
+ label: "Entry Type" | t,
+ instructions: 'Choose the Entry Type you want to save your feed data into.' | t,
+ class: 'element-child-group',
+ id: 'elementGroup-' ~ elementType ~ '-entryType',
+ name: 'elementGroup[' ~ elementType ~ '][entryType]',
+ options: craft.feedme.getSelectOptions(sectionEntryTypes),
+ value: entryTypeId ?? '',
+ required: true,
+ }) }}
+
diff --git a/feedme/templates/_includes/elements/entry/map.html b/feedme/templates/_includes/elements/entry/map.html
new file mode 100644
index 00000000..b8eb6ca9
--- /dev/null
+++ b/feedme/templates/_includes/elements/entry/map.html
@@ -0,0 +1,162 @@
+{% if feed.elementGroup %}
+ {% set sectionId = feed.elementGroup[elementType].section %}
+ {% set entryTypeId = feed.elementGroup[elementType].entryType %}
+
+ {% set section = craft.sections.getSectionById(sectionId) %}
+ {% set entryType = craft.feedMe.getEntryTypeById(entryTypeId) %}
+{% endif %}
+
+{% set users = [{ label: 'Don\'t import', value: '' }] %}
+{% if CraftEdition >= CraftClient %}
+ {% for user in craft.users.can('createEntries:' ~ sectionId) %}
+ {% set users = users | merge([{ label: user, value: user.id }]) %}
+ {% endfor %}
+{% endif %}
+
+{# If a structure, provide the option for a parent #}
+{% if section.type == 'structure' %}
+ {% set entries = [{ label: 'Don\'t import', value: '' }] %}
+ {% for entry in craft.entries %}
+ {% set entries = entries | merge([{ label: entry.title | slice(0, 40), value: entry.id }]) %}
+ {% endfor %}
+
+ {% set parentField = {
+ label: 'Parent' | t,
+ handle: 'parent',
+ instructions: 'Select a parent entry to import these entries under.' | t,
+ default: forms.selectField({
+ id: 'fieldDefaults-parent',
+ name: 'fieldDefaults[parent]',
+ value: feed.fieldDefaults.parent ?? '',
+ options: entries,
+ }),
+ } %}
+{% else %}
+ {% set parentField = {} %}
+{% endif %}
+
+
+{% set fields = [{
+ label: 'Title' | t,
+ handle: 'title',
+ default: forms.textField({
+ id: 'fieldDefaults-title',
+ name: 'fieldDefaults[title]',
+ value: feed.fieldDefaults.title ?? '',
+ }),
+}, {
+ label: 'Slug' | t,
+ handle: 'slug',
+ instructions: 'If not set, the Slug will be automatically created from Title.' | t,
+}, parentField, {
+ label: 'Post Date' | t,
+ handle: 'postDate',
+ instructions: 'Accepts Unix timestamp, or just about any English textual datetime description.' | t,
+ default: forms.dateTimeField({
+ id: 'fieldDefaults-postDate',
+ name: 'fieldDefaults[postDate]',
+ value: feed.fieldDefaults.postDate is defined ? craft.feedme.formatDateTime(feed.fieldDefaults.postDate) : '',
+ }),
+}, {
+ label: 'Expiry Date' | t,
+ handle: 'expiryDate',
+ instructions: 'Accepts Unix timestamp, or just about any English textual datetime description.' | t,
+ default: forms.dateTimeField({
+ id: 'fieldDefaults-expiryDate',
+ name: 'fieldDefaults[expiryDate]',
+ value: feed.fieldDefaults.expiryDate is defined ? craft.feedme.formatDateTime(feed.fieldDefaults.expiryDate) : '',
+ }),
+}, {
+ label: 'Status' | t,
+ handle: 'enabled',
+ instructions: 'Choose either a default status from the list or the imported field that will contain the status.' | t,
+ default: forms.selectField({
+ id: 'fieldDefaults-enabled',
+ name: 'fieldDefaults[enabled]',
+ value: feed.fieldDefaults.enabled is defined ? feed.fieldDefaults.enabled : '',
+ options: [
+ { label: 'Don\'t import', value: '' },
+ { label: 'Enabled', value: '1' },
+ { label: 'Disabled', value: '0' },
+ ],
+ }),
+}, {
+ label: 'Author' | t,
+ handle: 'authorId',
+ instructions: 'Entries will be assigned to the user in this field. If the field does not match any existing member, the default author will be assigned.' | t,
+ default: forms.selectField({
+ id: 'fieldDefaults-authorId',
+ name: 'fieldDefaults[authorId]',
+ value: feed.fieldDefaults.authorId ?? '',
+ options: users,
+ }),
+}, {
+ label: 'Entry ID' | t,
+ handle: 'id',
+ instructions: 'Warning: This should only be used for an existing Craft Entry ID.' | t,
+}] %}
+
+
{{ 'Entry Fields' | t }}
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for field in fields if field %}
+ {% if field.handle == 'parent' %}
+ {{ feedMeMacro.generateElementRow(_context, field) }}
+ {% else %}
+ {{ feedMeMacro.generateRow(_context, field) }}
+ {% endif %}
+ {% endfor %}
+
+
+
+{% for tab in craft.fields.getLayoutById(entryType.fieldLayoutId).getTabs() %}
+
+
+
{{ tab.name }} Fields
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set variables = { field: field, fieldtype: fieldtype, feed: feed, feedData: feedData } %}
+ {% include 'feedme/_includes/field' with variables %}
+ {% endfor %}
+
+
+{% endfor %}
+
+
+
+
{{ "Set a unique identifier for existing elements" | t }}
+
+
{{ "Select the fields you want to use to check for existing elements. When selected, Feed Me will look for existing elements that match the fields provided below and either update, or skip depending on your choice of Import Strategy." | t }}
+
+{% for tab in craft.fields.getLayoutById(entryType.fieldLayoutId).getTabs() %}
+ {% for fieldtype in tab.getFields() %}
+ {% set field = fieldtype.getField() %}
+
+ {% set fields = fields | merge([{ label: field.name, handle: field.handle }]) %}
+ {% endfor %}
+{% endfor %}
+
+
+ {% for field in fields if field %}
+ {{ forms.checkboxField({
+ name: 'fieldUnique[' ~ field.handle ~ ']',
+ label: field.label,
+ checked: feed.fieldUnique[field.handle] ?? '',
+ }) }}
+ {% endfor %}
+
diff --git a/feedme/templates/_includes/elements/user/column.html b/feedme/templates/_includes/elements/user/column.html
new file mode 100644
index 00000000..b10e73a4
--- /dev/null
+++ b/feedme/templates/_includes/elements/user/column.html
@@ -0,0 +1,3 @@
+{% set group = craft.userGroups.getGroupById(feed.elementGroup[elementType]) %}
+
+{{ group.name ?? '' }}
\ No newline at end of file
diff --git a/feedme/templates/_includes/elements/user/groups.html b/feedme/templates/_includes/elements/user/groups.html
new file mode 100644
index 00000000..6bee6d21
--- /dev/null
+++ b/feedme/templates/_includes/elements/user/groups.html
@@ -0,0 +1,10 @@
+{% set groups = craft.feedMe.getElementTypeGroups(elementType) %}
+
+{{ forms.selectField({
+ label: "User Group" | t,
+ instructions: 'Choose the user group you want to save your feed data into.' | t,
+ id: 'elementGroup-' ~ elementType,
+ name: 'elementGroup[' ~ elementType ~ ']',
+ options: craft.feedme.getSelectOptions(groups),
+ value: feed.elementGroup[elementType] ?? '',
+}) }}
diff --git a/feedme/templates/_includes/elements/user/map.html b/feedme/templates/_includes/elements/user/map.html
new file mode 100644
index 00000000..054406f4
--- /dev/null
+++ b/feedme/templates/_includes/elements/user/map.html
@@ -0,0 +1,130 @@
+{% if feed.elementGroup %}
+ {% set groupId = feed.elementGroup[elementType] %}
+
+ {% set group = craft.userGroups.getGroupById(groupId) %}
+{% endif %}
+
+{% set fields = [{
+ label: 'Username',
+ handle: 'username',
+ default: forms.textField({
+ id: 'fieldDefaults-username',
+ name: 'fieldDefaults[username]',
+ value: feed.fieldDefaults.username ?? '',
+ }),
+}, {
+ label: 'First Name',
+ handle: 'firstName',
+ default: forms.textField({
+ id: 'fieldDefaults-firstName',
+ name: 'fieldDefaults[firstName]',
+ value: feed.fieldDefaults.firstName ?? '',
+ }),
+}, {
+ label: 'Last Name',
+ handle: 'lastName',
+ default: forms.textField({
+ id: 'fieldDefaults-lastName',
+ name: 'fieldDefaults[lastName]',
+ value: feed.fieldDefaults.lastName ?? '',
+ }),
+}, {
+ label: 'Email',
+ handle: 'email',
+ default: forms.textField({
+ id: 'fieldDefaults-email',
+ name: 'fieldDefaults[email]',
+ value: feed.fieldDefaults.email ?? '',
+ }),
+}, {
+ label: 'Photo',
+ handle: 'photo',
+}, {
+ label: 'Preferred Locale',
+ handle: 'preferredLocale',
+}, {
+ label: 'Password',
+ handle: 'newPassword',
+}, {
+ label: 'Status',
+ handle: 'status',
+ instructions: 'Choose either a default status from the list or the imported field that will contain the status.' | t,
+ default: forms.selectField({
+ id: 'fieldDefaults-status',
+ name: 'fieldDefaults[status]',
+ value: feed.fieldDefaults.status ?? '',
+ options: [
+ { label: 'Don\'t import', value: '' },
+ { label: 'Active', value: 'active' },
+ { label: 'Locked', value: 'locked' },
+ { label: 'Suspended', value: 'suspended' },
+ { label: 'Archived', value: 'archived' },
+ { label: 'Pending', value: 'pending' },
+ ],
+ }),
+}, {
+ label: 'User ID' | t,
+ handle: 'id',
+ instructions: 'Warning: This should only be used for an existing Craft User ID.' | t,
+}] %}
+
+
{{ 'User Fields' | t }}
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for field in fields %}
+ {{ feedMeMacro.generateRow(_context, field) }}
+ {% endfor %}
+
+
+
+{% set profileFields = craft.fields.getLayoutByType('User').getFields() %}
+
+{% if profileFields %}
+
+
+
{{ 'Profile Fields' | t }}
+
+
+
+
{{ 'Field' | t }}
+
{{ 'Feed Element' | t }}
+
{{ 'Default Value' | t }}
+
+
+ {% for fieldtype in profileFields %}
+ {% set field = fieldtype.getField() %}
+
+ {% set variables = { field: field, fieldtype: fieldtype, feed: feed, feedData: feedData } %}
+ {% include 'feedme/_includes/field' with variables %}
+ {% endfor %}
+
+
+{% endif %}
+
+
+
+
{{ "Set a unique identifier for existing elements" | t }}
+
+
{{ "Select the fields you want to use to check for existing elements. When selected, Feed Me will look for existing elements that match the fields provided below and either update, or skip depending on your choice of Import Strategy." | t }}
+
+{% for fieldtype in profileFields %}
+ {% set field = fieldtype.getField() %}
+
+ {% set fields = fields | merge([{ label: field.name, handle: field.handle }]) %}
+{% endfor %}
+
+
diff --git a/feedme/templates/_includes/field.html b/feedme/templates/_includes/field.html
new file mode 100644
index 00000000..438fe2d7
--- /dev/null
+++ b/feedme/templates/_includes/field.html
@@ -0,0 +1,43 @@
+{% if handlePrefix is defined %}
+ {% set fieldHandle = handlePrefix ~ field.handle %}
+{% else %}
+ {% set fieldHandle = field.handle %}
+{% endif %}
+
+{% if labelName is not defined %}
+ {% set labelName = field.name %}
+{% endif %}
+
+{% if labelHandle is not defined %}
+ {% set labelHandle = field.handle %}
+{% endif %}
+
+{% set defaultTemplate = 'feedme/_includes/fields/default' %}
+{% set fieldTemplate = craft.feedMe.getFieldMapping(field.type) %}
+
+{# Include all fields that can be mapped inside this element #}
+{# Without some further refactoring, we can only support a subset of fields at the moment #}
+{% set supportedSubElementFields = [
+ 'Checkboxes',
+ 'Color',
+ 'Date',
+ 'Dropdown',
+ 'Lightswitch',
+ 'Multiselect',
+ 'Number',
+ 'PlainText',
+ 'PositionSelect',
+ 'Radio',
+ 'RichText',
+] %}
+
+{% set variables = {
+ field: field,
+ feed: feed,
+ feedData: feedData,
+ fieldHandle: fieldHandle,
+ labelName: labelName,
+ labelHandle: labelHandle,
+} %}
+
+{% include [fieldTemplate, defaultTemplate] ignore missing with variables %}
diff --git a/feedme/templates/_includes/fields/assets.html b/feedme/templates/_includes/fields/assets.html
new file mode 100644
index 00000000..aff8cb0f
--- /dev/null
+++ b/feedme/templates/_includes/fields/assets.html
@@ -0,0 +1,78 @@
+
\ No newline at end of file
diff --git a/feedme/templates/_includes/fields/entries.html b/feedme/templates/_includes/fields/entries.html
new file mode 100644
index 00000000..49aab849
--- /dev/null
+++ b/feedme/templates/_includes/fields/entries.html
@@ -0,0 +1,79 @@
+
+ {{ 'Data provided for this element is:' | t }}
+
+ {{ forms.selectField({
+ name: fieldHandle ~ '-options-match',
+ options: [
+ { value: 'id', label: 'ID' | t },
+ { value: 'title', label: 'Title' | t },
+ { value: 'slug', label: 'Slug' | t },
+ ],
+ value: global.feed.fieldMapping[fieldHandle ~ '-options-match'] ?? '',
+ }) }}
+
+
+ {% endnamespace %}
+
+
+
+
+ {% if variables.default is defined %}
+ {{ variables.default | raw }}
+ {% endif %}
+
+
+
+{% endmacro %}
+
+
+
+{% macro checkbox(config) %}
+ {%- spaceless %}
+
+ {% set value = (config.value is defined ? config.value : 1) %}
+ {% set id = (config.id is defined and config.id ? config.id : 'checkbox' ~ random()) %}
+ {% set label = (config.label is defined ? config.label) %}
+
+