diff --git a/feeds.api.php b/feeds.api.php
index 58e1670..a23bd2f 100644
--- a/feeds.api.php
+++ b/feeds.api.php
@@ -285,8 +285,8 @@ function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) {
// Specify both summary_callback and form_callback to add a per mapping
// configuration form.
- 'summary_callback' => 'my_module_summary_callback',
- 'form_callback' => 'my_module_form_callback',
+ 'summary_callbacks' => array('my_module_summary_callback'),
+ 'form_callbacks' => array('my_module_form_callback'),
);
$targets['my_node_field2'] = array(
'name' => t('My Second custom node field'),
@@ -334,23 +334,23 @@ function my_module_set_target($source, $entity, $target, array $values, $mapping
* Example of the summary_callback specified in
* hook_feeds_processor_targets_alter().
*
- * @param $mapping
+ * @param array $mapping
* Associative array of the mapping settings.
- * @param $target
+ * @param string $target
* Array of target settings, as defined by the processor or
* hook_feeds_processor_targets_alter().
- * @param $form
+ * @param array $form
* The whole mapping form.
- * @param $form_state
+ * @param array $form_state
* The form state of the mapping form.
*
- * @return
+ * @return string
* Returns, as a string that may contain HTML, the summary to display while
* the full form isn't visible.
* If the return value is empty, no summary and no option to view the form
* will be displayed.
*/
-function my_module_summary_callback($mapping, $target, $form, $form_state) {
+function my_module_summary_callback(array $mapping, $target, array $form, array $form_state) {
if (empty($mapping['my_setting'])) {
return t('My setting not active');
}
@@ -365,13 +365,13 @@ function my_module_summary_callback($mapping, $target, $form, $form_state) {
*
* The arguments are the same that my_module_summary_callback() gets.
*
- * @return
+ * @return array
* The per mapping configuration form. Once the form is saved, $mapping will
* be populated with the form values.
*
* @see my_module_summary_callback()
*/
-function my_module_form_callback($mapping, $target, $form, $form_state) {
+function my_module_form_callback(array $mapping, $target, array $form, array $form_state) {
return array(
'my_setting' => array(
'#type' => 'checkbox',
diff --git a/feeds.feeds.inc b/feeds.feeds.inc
new file mode 100644
index 0000000..fec42f8
--- /dev/null
+++ b/feeds.feeds.inc
@@ -0,0 +1,87 @@
+ '',
+ 'summary_callbacks' => array(),
+ 'form_callbacks' => array(),
+ 'preprocess_callbacks' => array(),
+ 'postprocess_callbacks' => array(),
+ 'unique_callbacks' => array(),
+ );
+
+ foreach (array_keys($targets) as $target) {
+ $targets[$target] += $defaults;
+
+ // Filter out any uncallable keys.
+ _feeds_filter_callback_arrays($targets[$target]);
+ }
+}
+
+/**
+ * Filters the callbacks of a single target array.
+ *
+ * @param array &$target
+ * The target arary.
+ */
+function _feeds_filter_callback_arrays(array &$target) {
+ static $normalize = array('summary_callback', 'form_callback');
+
+ // Migrate keys summary_callback and form_callback to the new keys.
+ foreach ($normalize as $key) {
+ if (!empty($target[$key])) {
+ $target[$key . 's'][] = $target[$key];
+ }
+
+ unset($target[$key]);
+ }
+
+ static $callback_keys = array(
+ 'summary_callbacks',
+ 'form_callbacks',
+ 'preprocess_callbacks',
+ 'unique_callbacks',
+ );
+
+ // Filter out any incorect callbaks. Do it here so it only has to be done
+ // once.
+ foreach ($callback_keys as $callback_key) {
+ $target[$callback_key] = array_filter($target[$callback_key], 'is_callable');
+ }
+
+ // This makes checking in FeedsProcessor::mapToTarget() simpler.
+ if (empty($target['callback']) || !is_callable($target['callback'])) {
+ unset($target['callback']);
+ }
+}
diff --git a/feeds.info b/feeds.info
index be3f52a..57eaabf 100644
--- a/feeds.info
+++ b/feeds.info
@@ -36,6 +36,7 @@ files[] = tests/feeds_mapper_date.test
files[] = tests/feeds_mapper_date_multiple.test
files[] = tests/feeds_mapper_field.test
files[] = tests/feeds_mapper_file.test
+files[] = tests/feeds_mapper_hooks.test
files[] = tests/feeds_mapper_link.test
files[] = tests/feeds_mapper_path.test
files[] = tests/feeds_mapper_profile.test
diff --git a/feeds.module b/feeds.module
index f536926..efbc703 100644
--- a/feeds.module
+++ b/feeds.module
@@ -35,6 +35,7 @@ function feeds_hook_info() {
'feeds_after_save',
'feeds_after_import',
'feeds_after_clear',
+ 'feeds_processor_targets',
'feeds_processor_targets_alter',
'feeds_parser_sources_alter',
);
@@ -785,6 +786,36 @@ function feeds_system_info_alter(array &$info, $file, $type) {
}
/**
+ * Implements hook_module_implements_alter().
+ */
+function feeds_module_implements_alter(array &$implementations, $hook) {
+ if ($hook === 'feeds_processor_targets_alter') {
+ // We need two implementations of this hook, so we add one that gets
+ // called first, and move the normal one to last.
+ $implementations = array('_feeds' => FALSE) + $implementations;
+
+ // Move normal implementation to last.
+ $group = $implementations['feeds'];
+ unset($implementations['feeds']);
+ $implementations['feeds'] = $group;
+ }
+}
+
+/**
+ * Implements hook_feeds_processor_targets_alter().
+ *
+ * @see feeds_feeds_processor_targets()
+ * @see feeds_feeds_processor_targets_alter()
+ */
+function _feeds_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) {
+ // If hook_feeds_processor_targets() hasn't been called, for instance, by
+ // older processors, invoke it ourself.
+ if (!drupal_static('feeds_feeds_processor_targets', FALSE)) {
+ $targets += module_invoke_all('feeds_processor_targets', $entity_type, $bundle);
+ }
+}
+
+/**
* @}
*/
diff --git a/feeds.pages.inc b/feeds.pages.inc
index eeeab90..99a601e 100644
--- a/feeds.pages.inc
+++ b/feeds.pages.inc
@@ -232,7 +232,9 @@ function feeds_unlock_tab_form($form, &$form_state, FeedsImporter $importer = NU
'#value' => feeds_source_status($source),
);
$form = confirm_form($form, t('Unlock this importer?'), $form['#redirect'], '', t('Delete'), t('Cancel'), 'confirm feeds update');
- if ($source->progressImporting() == FEEDS_BATCH_COMPLETE && $source->progressClearing() == FEEDS_BATCH_COMPLETE) {
+ $locked = !lock_may_be_available("feeds_source_{$source->id}_{$source->feed_nid}");
+
+ if (!$locked) {
$form['source_locked'] = array(
'#type' => 'markup',
'#title' => t('Not Locked'),
diff --git a/feeds_ui/feeds_ui.admin.inc b/feeds_ui/feeds_ui.admin.inc
index b174d75..6a794b8 100644
--- a/feeds_ui/feeds_ui.admin.inc
+++ b/feeds_ui/feeds_ui.admin.inc
@@ -634,12 +634,10 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe
}
if ($form_state['mapping_settings_edit'] === $i) {
- // Build the form.
- if (isset($target['form_callback'])) {
- $settings_form = call_user_func($target['form_callback'], $mapping, $target, $form, $form_state);
- }
- else {
- $settings_form = array();
+ $settings_form = array();
+
+ foreach ($target['form_callbacks'] as $callback) {
+ $settings_form += $callback($mapping, $target, $form, $form_state);
}
// Merge in the optional unique form.
@@ -664,13 +662,15 @@ function feeds_ui_mapping_settings_form($form, $form_state, $i, $mapping, $targe
}
else {
// Build the summary.
- if (isset($target['summary_callback'])) {
- $summary = call_user_func($target['summary_callback'], $mapping, $target, $form, $form_state);
- }
- else {
- $summary = '';
+ $summary = array();
+
+ foreach ($target['summary_callbacks'] as $callback) {
+ $summary[] = $callback($mapping, $target, $form, $form_state);
}
+ // Filter out empty summary values.
+ $summary = implode('
', array_filter($summary));
+
// Append the optional unique summary.
if ($optional_unique_summary = feeds_ui_mapping_settings_optional_unique_summary($mapping, $target, $form, $form_state)) {
$summary .= ' ' . $optional_unique_summary;
diff --git a/mappers/date.inc b/mappers/date.inc
index 061962d..c75f125 100644
--- a/mappers/date.inc
+++ b/mappers/date.inc
@@ -6,13 +6,13 @@
*/
/**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsNodeProcessor::getMappingTargets().
+ * Implements hook_feeds_processor_targets().
*
* @todo Only provides "end date" target if field allows it.
*/
-function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function date_feeds_processor_targets($entity_type, $bundle_name) {
+ $targets = array();
+
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
$info = field_info_field($name);
if (in_array($info['type'], array('date', 'datestamp', 'datetime'))) {
@@ -30,12 +30,14 @@ function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
);
}
}
+
+ return $targets;
}
/**
- * Callback for setting target values.
+ * Callback for setting date values.
*/
-function date_feeds_set_target($source, $entity, $target, array $values) {
+function date_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
list($field_name, $sub_field) = explode(':', $target, 2);
$delta = 0;
@@ -54,7 +56,7 @@ function date_feeds_set_target($source, $entity, $target, array $values) {
}
}
- $value->buildDateField($entity, $field_name, $delta);
+ $value->buildDateField($entity, $field_name, $delta, $mapping['language']);
$delta++;
}
}
diff --git a/mappers/entity_translation.inc b/mappers/entity_translation.inc
new file mode 100644
index 0000000..3d09bff
--- /dev/null
+++ b/mappers/entity_translation.inc
@@ -0,0 +1,84 @@
+feeds_item->entity_type;
+
+ if (!$handler = entity_translation_get_handler($entity_type, $entity)) {
+ return;
+ }
+
+ list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+
+ $languages_seen = array();
+
+ $entity_info = entity_get_info($entity_type);
+ $translation_key = isset($entity_info['entity keys']['translations']) ? $entity_info['entity keys']['translations'] : FALSE;
+
+ if (!$translation_key) {
+ return;
+ }
+
+ foreach (field_info_instances($entity_type, $bundle) as $instance) {
+ $field_name = $instance['field_name'];
+
+ // No values in this field, skip it.
+ if (empty($entity->$field_name)) {
+ continue;
+ }
+
+ // Not translatable.
+ if (($info = field_info_field($field_name)) && !$info['translatable']) {
+ continue;
+ }
+
+ // Init the translation handler.
+ if (empty($entity->$translation_key->data)) {
+ $handler->initTranslations();
+ }
+
+ // Avoid invalid user configuration. Entity translation does this when
+ // loading the translation overview page.
+ if (count($entity->$field_name) === 1 && key($entity->$field_name) === LANGUAGE_NONE) {
+ $entity->{$field_name}[$handler->getLanguage()] = $entity->{$field_name}[LANGUAGE_NONE];
+ unset($entity->{$field_name}[LANGUAGE_NONE]);
+ }
+
+ // Look at all languages in this field.
+ foreach ($entity->$field_name as $language => $v) {
+ if (isset($languages_seen[$language]) || $language === LANGUAGE_NONE) {
+ continue;
+ }
+
+ $languages_seen[$language] = TRUE;
+
+ if ($language === $handler->getLanguage()) {
+ continue;
+ }
+
+ $translation = array(
+ 'translate' => 0,
+ 'status' => 1,
+ 'language' => $language,
+ 'source' => $handler->getLanguage(),
+ );
+
+ $handler->setTranslation($translation, $entity);
+ }
+ }
+
+ // Loop through every language for the site, and remove translations for the
+ // ones that don't have any values.
+ foreach (language_list() as $language) {
+ if (!isset($languages_seen[$language->language])) {
+ $handler->removeTranslation($language->language);
+ }
+ }
+}
diff --git a/mappers/file.inc b/mappers/file.inc
index 2273590..e1ee412 100644
--- a/mappers/file.inc
+++ b/mappers/file.inc
@@ -7,11 +7,11 @@
*/
/**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsNodeProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
*/
-function file_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function file_feeds_processor_targets($entity_type, $bundle_name) {
+ $targets = array();
+
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
$info = field_info_field($name);
@@ -47,16 +47,16 @@ function file_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
}
}
}
+
+ return $targets;
}
/**
- * Callback for mapping. Here is where the actual mapping happens.
- *
- * When the callback is invoked, $target contains the name of the field the
- * user has decided to map to and $value contains the value of the feed item
- * element the user has picked as a source.
+ * Callback for mapping file fields.
*/
-function file_feeds_set_target($source, $entity, $target, array $values) {
+function file_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
+ $language = $mapping['language'];
+
// Add default of uri for backwards compatibility.
list($field_name, $sub_field) = explode(':', $target . ':uri');
$info = field_info_field($field_name);
@@ -92,31 +92,31 @@ function file_feeds_set_target($source, $entity, $target, array $values) {
}
// Populate entity.
- $field = isset($entity->$field_name) ? $entity->$field_name : array(LANGUAGE_NONE => array());
+ $field = isset($entity->$field_name) ? $entity->$field_name : array($language => array());
$delta = 0;
foreach ($values as $v) {
if ($info['cardinality'] == $delta) {
break;
}
- if (!isset($field[LANGUAGE_NONE][$delta])) {
- $field[LANGUAGE_NONE][$delta] = array();
+ if (!isset($field[$language][$delta])) {
+ $field[$language][$delta] = array();
}
switch ($sub_field) {
case 'alt':
case 'title':
case 'description':
- $field[LANGUAGE_NONE][$delta][$sub_field] = $v;
+ $field[$language][$delta][$sub_field] = $v;
break;
case 'uri':
if ($v) {
try {
$file = $v->getFile($destination);
- $field[LANGUAGE_NONE][$delta] += (array) $file;
+ $field[$language][$delta] += (array) $file;
// @todo: Figure out how to properly populate this field.
- $field[LANGUAGE_NONE][$delta]['display'] = 1;
+ $field[$language][$delta]['display'] = 1;
}
catch (Exception $e) {
watchdog_exception('Feeds', $e, nl2br(check_plain($e)));
diff --git a/mappers/link.inc b/mappers/link.inc
index 90b5268..1dc19dc 100644
--- a/mappers/link.inc
+++ b/mappers/link.inc
@@ -6,11 +6,11 @@
*/
/**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
*/
-function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function link_feeds_processor_targets($entity_type, $bundle_name) {
+ $targets = array();
+
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
$info = field_info_field($name);
if ($info['type'] == 'link_field') {
@@ -32,19 +32,19 @@ function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
}
}
}
+
+ return $targets;
}
/**
- * Callback for mapping. Here is where the actual mapping happens.
- *
- * When the callback is invoked, $target contains the name of the field the
- * user has decided to map to and $value contains the value of the feed item
- * element the user has picked as a source.
+ * Callback for mapping link fields.
*/
-function link_feeds_set_target($source, $entity, $target, array $values) {
+function link_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
+ $language = $mapping['language'];
+
list($field_name, $column) = explode(':', $target);
- $field = isset($entity->$field_name) ? $entity->$field_name : array('und' => array());
+ $field = isset($entity->$field_name) ? $entity->$field_name : array($language => array());
$delta = 0;
foreach ($values as $value) {
@@ -53,7 +53,7 @@ function link_feeds_set_target($source, $entity, $target, array $values) {
}
if (is_scalar($value)) {
- $field['und'][$delta][$column] = (string) $value;
+ $field[$language][$delta][$column] = (string) $value;
}
$delta++;
}
diff --git a/mappers/list.inc b/mappers/list.inc
index 6a8a168..6a30829 100644
--- a/mappers/list.inc
+++ b/mappers/list.inc
@@ -6,11 +6,11 @@
*/
/**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
*/
-function list_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function list_feeds_processor_targets($entity_type, $bundle_name) {
+ $targets = array();
+
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
$info = field_info_field($name);
@@ -42,13 +42,17 @@ function list_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
break;
}
}
+
+ return $targets;
}
/**
* Callback for setting list_boolean fields.
*/
-function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, array $values, array $mapping = array()) {
- $field = isset($entity->$target) ? $entity->$target : array(LANGUAGE_NONE => array());
+function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
+ $language = $mapping['language'];
+
+ $field = isset($entity->$target) ? $entity->$target : array($language => array());
foreach ($values as $value) {
@@ -56,7 +60,7 @@ function list_feeds_set_boolean_target(FeedsSource $source, $entity, $target, ar
$value = $value->getValue();
}
- $field[LANGUAGE_NONE][] = array('value' => (int) (bool) $value);
+ $field[$language][] = array('value' => (int) (bool) $value);
}
$entity->$target = $field;
diff --git a/mappers/locale.inc b/mappers/locale.inc
new file mode 100644
index 0000000..4d18dcc
--- /dev/null
+++ b/mappers/locale.inc
@@ -0,0 +1,90 @@
+processor->entityType();
+ $translatable = _locale_feeds_target_is_translatable($entity_type, $mapping['target']);
+
+ $mapping += array('field_language' => LANGUAGE_NONE);
+
+ // This is an invalid configuration that can come from disabling
+ // entity_translation.
+ $error = $mapping['field_language'] !== LANGUAGE_NONE && !$translatable;
+
+ // Nothing to see here.
+ if (!$error && !$translatable) {
+ return;
+ }
+
+ if ($error) {
+ return t('Language: @error', array('@error' => t('Error')));
+ }
+
+ $language_options = array(LANGUAGE_NONE => t('Language neutral')) + locale_language_list('name');
+
+ return t('Language: %lang', array('%lang' => $language_options[$mapping['field_language']]));
+}
+
+function locale_feeds_form_callback(array $mapping, array $target, array $form, array $form_state) {
+ $form = array();
+
+ $entity_type = $form_state['build_info']['args'][0]->processor->entityType();
+
+ $translatable = _locale_feeds_target_is_translatable($entity_type, $mapping['target']);
+ $mapping += array('field_language' => LANGUAGE_NONE);
+
+ // This is an invalid configuration that can come from disabling
+ // entity_translation.
+ $error = $mapping['field_language'] !== LANGUAGE_NONE && !$translatable;
+
+ // Nothing to see here.
+ if (!$error && !$translatable) {
+ return $form;
+ }
+
+ $language_options = array(LANGUAGE_NONE => t('Language neutral'));
+
+ if (!$error) {
+ $language_options += locale_language_list('name');
+ }
+
+ $form['field_language'] = array(
+ '#type' => 'select',
+ '#title' => t('Language'),
+ '#options' => $language_options,
+ '#default_value' => $mapping['field_language'],
+ );
+
+ return $form;
+}
+
+function _locale_feeds_target_is_translatable($entity_type, $target) {
+ list($field_name) = explode(':', $target, 2);
+
+ $info = field_info_field($field_name);
+
+ return !empty($info) && field_is_translatable($entity_type, $info);
+}
diff --git a/mappers/number.inc b/mappers/number.inc
index 2b9c436..406b4f8 100644
--- a/mappers/number.inc
+++ b/mappers/number.inc
@@ -6,11 +6,11 @@
*/
/**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
*/
-function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function number_feeds_processor_targets($entity_type, $bundle_name) {
+ $targets = array();
+
$numeric_types = array(
'number_integer',
'number_decimal',
@@ -27,16 +27,18 @@ function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_n
);
}
}
+
+ return $targets;
}
/**
- * Callback for mapping numerics.
- *
- * Ensure that $value is a numeric to avoid database errors.
+ * Callback for mapping number fields.
*/
-function number_feeds_set_target($source, $entity, $target, array $values) {
+function number_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
+ $language = $mapping['language'];
+
// Iterate over all values.
- $field = isset($entity->$target) ? $entity->$target : array('und' => array());
+ $field = isset($entity->$target) ? $entity->$target : array($language => array());
foreach ($values as $value) {
@@ -45,7 +47,7 @@ function number_feeds_set_target($source, $entity, $target, array $values) {
}
if (is_numeric($value)) {
- $field['und'][] = array('value' => $value);
+ $field[$language][] = array('value' => $value);
}
}
diff --git a/mappers/path.inc b/mappers/path.inc
index cd39fb1..1dceeea 100644
--- a/mappers/path.inc
+++ b/mappers/path.inc
@@ -6,11 +6,11 @@
*/
/**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsNodeProcessor::getMappingTargets().
+ * Implements hook_feeds_processor_targets().
*/
-function path_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function path_feeds_processor_targets($entity_type, $bundle_name) {
+ $targets = array();
+
switch ($entity_type) {
case 'node':
case 'taxonomy_term':
@@ -19,21 +19,19 @@ function path_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
'name' => t('Path alias'),
'description' => t('URL path alias of the node.'),
'callback' => 'path_feeds_set_target',
- 'summary_callback' => 'path_feeds_summary_callback',
- 'form_callback' => 'path_feeds_form_callback',
+ 'summary_callbacks' => array('path_feeds_summary_callback'),
+ 'form_callbacks' => array('path_feeds_form_callback'),
);
break;
}
+
+ return $targets;
}
/**
- * Callback for mapping. Here is where the actual mapping happens.
- *
- * When the callback is invoked, $target contains the name of the field the
- * user has decided to map to and $value contains the value of the feed item
- * element the user has picked as a source.
+ * Callback for mapping path aliases.
*/
-function path_feeds_set_target($source, $entity, $target, array $values, $mapping) {
+function path_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
$alias = FALSE;
// Path alias cannot be multi-valued, so use the first non-empty value.
foreach ($values as $value) {
@@ -68,24 +66,8 @@ function path_feeds_set_target($source, $entity, $target, array $values, $mappin
/**
* Mapping configuration summary for path.module.
- *
- * @param $mapping
- * Associative array of the mapping settings.
- * @param $target
- * Array of target settings, as defined by the processor or
- * hook_feeds_processor_targets_alter().
- * @param $form
- * The whole mapping form.
- * @param $form_state
- * The form state of the mapping form.
- *
- * @return
- * Returns, as a string that may contain HTML, the summary to display while
- * the full form isn't visible.
- * If the return value is empty, no summary and no option to view the form
- * will be displayed.
*/
-function path_feeds_summary_callback($mapping, $target, $form, $form_state) {
+function path_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) {
if (!module_exists('pathauto')) {
return;
}
@@ -101,12 +83,8 @@ function path_feeds_summary_callback($mapping, $target, $form, $form_state) {
/**
* Settings form callback.
- *
- * @return
- * The per mapping configuration form. Once the form is saved, $mapping will
- * be populated with the form values.
*/
-function path_feeds_form_callback($mapping, $target, $form, $form_state) {
+function path_feeds_form_callback(array $mapping, $target, array $form, array $form_state) {
return array(
'pathauto_override' => array(
'#type' => 'checkbox',
diff --git a/mappers/profile.inc b/mappers/profile.inc
index 00fdf3a..42f8e88 100644
--- a/mappers/profile.inc
+++ b/mappers/profile.inc
@@ -2,16 +2,17 @@
/**
* @file
- * On behalf implementation of Feeds mapping API for user profiles.
+ * On behalf implementation of Feeds mapping API for profile.module.
*/
/**
- * Implements hook_feeds_processor_target_alter().
+ * Implements hook_feeds_processor_targets().
*/
-function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function profile_feeds_processor_targets($entity_type, $bundle_name) {
+ $targets = array();
if ($entity_type != 'user') {
- return;
+ return $targets;
}
$categories = profile_user_categories();
@@ -25,11 +26,13 @@ function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_
);
}
}
+
+ return $targets;
}
/**
* Set the user profile target after import.
*/
-function profile_feeds_set_target($source, $entity, $target, array $values, $mapping) {
+function profile_feeds_set_target(FeedsSource $source, $entity, $target, array $values) {
$entity->$target = reset($values);
}
diff --git a/mappers/taxonomy.inc b/mappers/taxonomy.inc
index 75f6541..bd7fed4 100644
--- a/mappers/taxonomy.inc
+++ b/mappers/taxonomy.inc
@@ -23,7 +23,7 @@ define('FEEDS_TAXONOMY_SEARCH_TERM_GUID', 2);
/**
* Implements hook_feeds_parser_sources_alter().
*/
-function taxonomy_feeds_parser_sources_alter(&$sources, $content_type) {
+function taxonomy_feeds_parser_sources_alter(array &$sources, $content_type) {
if (!empty($content_type)) {
foreach (taxonomy_get_vocabularies($content_type) as $vocabulary) {
$sources['parent:taxonomy:' . $vocabulary->machine_name] = array(
@@ -55,9 +55,11 @@ function taxonomy_feeds_get_source(FeedsSource $source, FeedsParserResult $resul
}
/**
- * Implements hook_feeds_processor_targets_alter().
+ * Implements hook_feeds_processor_targets().
*/
-function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function taxonomy_feeds_processor_targets($entity_type, $bundle_name) {
+ $targets = array();
+
foreach (field_info_instances($entity_type, $bundle_name) as $name => $instance) {
$info = field_info_field($name);
if ($info['type'] == 'taxonomy_term_reference') {
@@ -65,8 +67,8 @@ function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle
'name' => check_plain($instance['label']),
'callback' => 'taxonomy_feeds_set_target',
'description' => t('The @label field of the entity.', array('@label' => $instance['label'])),
- 'summary_callback' => 'taxonomy_feeds_summary_callback',
- 'form_callback' => 'taxonomy_feeds_form_callback',
+ 'summary_callbacks' => array('taxonomy_feeds_summary_callback'),
+ 'form_callbacks' => array('taxonomy_feeds_form_callback'),
);
}
}
@@ -75,14 +77,16 @@ function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle
$targets['tid']['description'] = t('The tid of the taxonomy term. NOTE: use this feature with care, node ids are usually assigned by Drupal.');
unset($targets['vocabulary']);
}
+
+ return $targets;
}
/**
- * Callback for mapping. Here is where the actual mapping happens.
- *
- * @todo Do not create new terms for non-autotag fields.
+ * Callback for mapping taxonomy terms.
*/
-function taxonomy_feeds_set_target($source, $entity, $target, array $terms, $mapping = array()) {
+function taxonomy_feeds_set_target(FeedsSource $source, $entity, $target, array $terms, array $mapping) {
+ $language = $mapping['language'];
+
// Add in default values.
$mapping += array(
'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_NAME,
@@ -116,10 +120,10 @@ function taxonomy_feeds_set_target($source, $entity, $target, array $terms, $map
->range(0, 1);
- $field = isset($entity->$target) ? $entity->$target : array('und' => array());
+ $field = isset($entity->$target) ? $entity->$target : array($language => array());
// Allow for multiple mappings to the same target.
- $delta = count($field['und']);
+ $delta = count($field[$language]);
// Iterate over all values.
foreach ($terms as $term) {
@@ -179,7 +183,7 @@ function taxonomy_feeds_set_target($source, $entity, $target, array $terms, $map
}
if ($tid && isset($cache['allowed_values'][$target][$tid])) {
- $field['und'][] = array('tid' => $tid);
+ $field[$language][] = array('tid' => $tid);
$delta++;
}
}
@@ -252,24 +256,8 @@ function taxonomy_feeds_term_lookup_term_by_guid($guid) {
/**
* Mapping configuration summary for taxonomy.module.
- *
- * @param array $mapping
- * Associative array of the mapping settings.
- * @param array $target
- * Array of target settings, as defined by the processor or
- * hook_feeds_processor_targets_alter().
- * @param array $form
- * The whole mapping form.
- * @param array $form_state
- * The form state of the mapping form.
- *
- * @return string
- * Returns, as a string that may contain HTML, the summary to display while
- * the full form isn't visible.
- * If the return value is empty, no summary and no option to view the form
- * will be displayed.
*/
-function taxonomy_feeds_summary_callback($mapping, $target, $form, $form_state) {
+function taxonomy_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) {
$options = _taxonomy_feeds_form_callback_options();
if (empty($mapping['term_search'])) {
return t('Search taxonomy terms by: @search', array('@search' => $options[FEEDS_TAXONOMY_SEARCH_TERM_NAME]));
@@ -279,12 +267,8 @@ function taxonomy_feeds_summary_callback($mapping, $target, $form, $form_state)
/**
* Settings form callback.
- *
- * @return array
- * The per mapping configuration form. Once the form is saved, $mapping will
- * be populated with the form values.
*/
-function taxonomy_feeds_form_callback($mapping, $target, $form, $form_state) {
+function taxonomy_feeds_form_callback(array $mapping, $target, array $form, array $form_state) {
return array(
'term_search' => array(
'#type' => 'select',
diff --git a/mappers/text.inc b/mappers/text.inc
index 89a03ff..1cf76bf 100644
--- a/mappers/text.inc
+++ b/mappers/text.inc
@@ -6,11 +6,11 @@
*/
/**
- * Implements hook_feeds_processor_targets_alter().
- *
- * @see FeedsProcessor::getMappingTargets()
+ * Implements hook_feeds_processor_targets().
*/
-function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) {
+function text_feeds_processor_targets($entity_type, $bundle_name) {
+ $targets = array();
+
$text_types = array(
'text',
'text_long',
@@ -37,16 +37,20 @@ function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam
}
if (!empty($instance['settings']['text_processing'])) {
- $targets[$name]['summary_callback'] = 'text_feeds_summary_callback';
- $targets[$name]['form_callback'] = 'text_feeds_form_callback';
+ $targets[$name]['summary_callbacks'] = array('text_feeds_summary_callback');
+ $targets[$name]['form_callbacks'] = array('text_feeds_form_callback');
}
}
+
+ return $targets;
}
/**
* Callback for mapping text fields.
*/
-function text_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping = array()) {
+function text_feeds_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) {
+ $language = $mapping['language'];
+
list($field_name, $column) = explode(':', $target . ':value');
if ($column === 'value' && isset($source->importer->processor->config['input_format'])) {
@@ -57,7 +61,7 @@ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $val
);
}
- $field = isset($entity->$field_name) ? $entity->$field_name : array('und' => array());
+ $field = isset($entity->$field_name) ? $entity->$field_name : array($language => array());
// Iterate over all values.
$delta = 0;
@@ -69,10 +73,10 @@ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $val
if (is_scalar($value) && strlen($value)) {
- $field['und'][$delta][$column] = (string) $value;
+ $field[$language][$delta][$column] = (string) $value;
if (isset($mapping['format'])) {
- $field['und'][$delta]['format'] = $mapping['format'];
+ $field[$language][$delta]['format'] = $mapping['format'];
}
}
@@ -87,7 +91,7 @@ function text_feeds_set_target(FeedsSource $source, $entity, $target, array $val
*
* Displays which text format will be used for the text field target.
*
- * @see text_feeds_processor_targets_alter()
+ * @see text_feeds_processor_targets()
* @see text_feeds_form_callback()
*/
function text_feeds_summary_callback(array $mapping, $target, array $form, array $form_state) {
@@ -109,7 +113,7 @@ function text_feeds_summary_callback(array $mapping, $target, array $form, array
*
* Allows to select a text format for the text field target.
*
- * @see text_feeds_processor_targets_alter()
+ * @see text_feeds_processor_targets()
* @see text_feeds_summary_callback()
*/
function text_feeds_form_callback(array $mapping, $target, array $form, array $form_state) {
diff --git a/plugins/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc
index ec23016..d04d546 100644
--- a/plugins/FeedsNodeProcessor.inc
+++ b/plugins/FeedsNodeProcessor.inc
@@ -40,7 +40,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
$node->type = $this->bundle();
$node->changed = REQUEST_TIME;
$node->created = REQUEST_TIME;
- $node->language = LANGUAGE_NONE;
+ $node->language = $this->config['language'];
$node->is_new = TRUE;
node_object_prepare($node);
// Populate properties that are set by node_object_prepare().
@@ -339,11 +339,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
);
}
- // Let other modules expose mapping targets.
- self::loadMappers();
- $entity_type = $this->entityType();
- $bundle = $this->bundle();
- drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
+ $this->getHookTargets($targets);
return $targets;
}
diff --git a/plugins/FeedsParser.inc b/plugins/FeedsParser.inc
index c864f29..a8434bb 100644
--- a/plugins/FeedsParser.inc
+++ b/plugins/FeedsParser.inc
@@ -465,13 +465,13 @@ class FeedsDateTimeElement extends FeedsElement {
* Helper method for buildDateField(). Build a FeedsDateTimeElement object
* from a standard formatted node.
*/
- protected static function readDateField($entity, $field_name, $delta = 0) {
+ protected static function readDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) {
$ret = new FeedsDateTimeElement();
- if (isset($entity->{$field_name}['und'][$delta]['date']) && $entity->{$field_name}['und'][$delta]['date'] instanceof FeedsDateTime) {
- $ret->start = $entity->{$field_name}['und'][$delta]['date'];
+ if (isset($entity->{$field_name}[$language][$delta]['date']) && $entity->{$field_name}[$language][$delta]['date'] instanceof FeedsDateTime) {
+ $ret->start = $entity->{$field_name}[$language][$delta]['date'];
}
- if (isset($entity->{$field_name}['und'][$delta]['date2']) && $entity->{$field_name}['und'][$delta]['date2'] instanceof FeedsDateTime) {
- $ret->end = $entity->{$field_name}['und'][$delta]['date2'];
+ if (isset($entity->{$field_name}[$language][$delta]['date2']) && $entity->{$field_name}[$language][$delta]['date2'] instanceof FeedsDateTime) {
+ $ret->end = $entity->{$field_name}[$language][$delta]['date2'];
}
return $ret;
}
@@ -486,10 +486,10 @@ class FeedsDateTimeElement extends FeedsElement {
* @param int $delta
* The delta in the field.
*/
- public function buildDateField($entity, $field_name, $delta = 0) {
+ public function buildDateField($entity, $field_name, $delta = 0, $language = LANGUAGE_NONE) {
$info = field_info_field($field_name);
- $oldfield = FeedsDateTimeElement::readDateField($entity, $field_name, $delta);
+ $oldfield = FeedsDateTimeElement::readDateField($entity, $field_name, $delta, $language);
// Merge with any preexisting objects on the field; we take precedence.
$oldfield = $this->merge($oldfield);
$use_start = $oldfield->start;
@@ -522,27 +522,27 @@ class FeedsDateTimeElement extends FeedsElement {
$db_tz = new DateTimeZone($db_tz);
if (!isset($entity->{$field_name})) {
- $entity->{$field_name} = array('und' => array());
+ $entity->{$field_name} = array($language => array());
}
if ($use_start) {
- $entity->{$field_name}['und'][$delta]['timezone'] = $use_start->getTimezone()->getName();
- $entity->{$field_name}['und'][$delta]['offset'] = $use_start->getOffset();
+ $entity->{$field_name}[$language][$delta]['timezone'] = $use_start->getTimezone()->getName();
+ $entity->{$field_name}[$language][$delta]['offset'] = $use_start->getOffset();
$use_start->setTimezone($db_tz);
- $entity->{$field_name}['und'][$delta]['date'] = $use_start;
+ $entity->{$field_name}[$language][$delta]['date'] = $use_start;
/**
* @todo the date_type_format line could be simplified based upon a patch
* DO issue #259308 could affect this, follow up on at some point.
* Without this, all granularity info is lost.
* $use_start->format(date_type_format($field['type'], $use_start->granularity));
*/
- $entity->{$field_name}['und'][$delta]['value'] = $use_start->format(date_type_format($info['type']));
+ $entity->{$field_name}[$language][$delta]['value'] = $use_start->format(date_type_format($info['type']));
}
if ($use_end) {
// Don't ever use end to set timezone (for now)
- $entity->{$field_name}['und'][$delta]['offset2'] = $use_end->getOffset();
+ $entity->{$field_name}[$language][$delta]['offset2'] = $use_end->getOffset();
$use_end->setTimezone($db_tz);
- $entity->{$field_name}['und'][$delta]['date2'] = $use_end;
- $entity->{$field_name}['und'][$delta]['value2'] = $use_end->format(date_type_format($info['type']));
+ $entity->{$field_name}[$language][$delta]['date2'] = $use_end;
+ $entity->{$field_name}[$language][$delta]['value2'] = $use_end->format(date_type_format($info['type']));
}
}
}
diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc
index fe7c541..8f14447 100644
--- a/plugins/FeedsProcessor.inc
+++ b/plugins/FeedsProcessor.inc
@@ -586,6 +586,47 @@ abstract class FeedsProcessor extends FeedsPlugin {
}
/**
+ * Returns a statically cached version of the target mappings.
+ *
+ * @return array
+ * The targets for this importer.
+ */
+ protected function getCachedTargets() {
+ $targets = &drupal_static('FeedsProcessor::getCachedTargets', array());
+
+ if (!isset($targets[$this->id])) {
+ $targets[$this->id] = $this->getMappingTargets();
+ }
+
+ return $targets[$this->id];
+ }
+
+ /**
+ * Returns a statically cached version of the source mappings.
+ *
+ * @return array
+ * The sources for this importer.
+ */
+ protected function getCachedSources() {
+ $sources = &drupal_static('FeedsProcessor::getCachedSources', array());
+
+ if (!isset($sources[$this->id])) {
+
+ $sources[$this->id] = feeds_importer($this->id)->parser->getMappingSources();
+
+ if (is_array($sources[$this->id])) {
+ foreach ($sources[$this->id] as $source_key => $source) {
+ if (empty($source['callback']) || !is_callable($source['callback'])) {
+ unset($sources[$this->id][$source_key]['callback']);
+ }
+ }
+ }
+ }
+
+ return $sources[$this->id];
+ }
+
+ /**
* Execute mapping on an item.
*
* This method encapsulates the central mapping functionality. When an item is
@@ -603,82 +644,128 @@ abstract class FeedsProcessor extends FeedsPlugin {
* @ingroup mappingapi
*
* @see hook_feeds_parser_sources_alter()
- * @see hook_feeds_data_processor_targets_alter()
- * @see hook_feeds_node_processor_targets_alter()
- * @see hook_feeds_term_processor_targets_alter()
- * @see hook_feeds_user_processor_targets_alter()
+ * @see hook_feeds_processor_targets()
+ * @see hook_feeds_processor_targets_alter()
*/
protected function map(FeedsSource $source, FeedsParserResult $result, $target_item = NULL) {
+ $targets = $this->getCachedTargets();
- // Static cache $targets as getMappingTargets() may be an expensive method.
- static $sources;
- if (!isset($sources[$this->id])) {
- $sources[$this->id] = feeds_importer($this->id)->parser->getMappingSources();
- }
- static $targets;
- if (!isset($targets[$this->id])) {
- $targets[$this->id] = $this->getMappingTargets();
- }
- $parser = feeds_importer($this->id)->parser;
if (empty($target_item)) {
- $target_item = array();
+ $target_item = new stdClass();
}
+ $mappings = $this->config['mappings'];
// Many mappers add to existing fields rather than replacing them. Hence we
// need to clear target elements of each item before mapping in case we are
// mapping on a prepopulated item such as an existing node.
- foreach ($this->config['mappings'] as $mapping) {
- if (isset($targets[$this->id][$mapping['target']]['real_target'])) {
- $target_item->{$targets[$this->id][$mapping['target']]['real_target']} = NULL;
+ foreach ($mappings as $mapping) {
+ if (isset($targets[$mapping['target']]['real_target'])) {
+ $target_item->{$targets[$mapping['target']]['real_target']} = NULL;
}
else {
$target_item->{$mapping['target']} = NULL;
}
}
- /*
- This is where the actual mapping happens: For every mapping we envoke
- the parser's getSourceElement() method to retrieve the value of the source
- element and pass it to the processor's setTargetElement() to stick it
- on the right place of the target item.
+ // Invoke preprocess callbacks on targets.
+ $this->invokeProcessCallbacks('pre', $source, $target_item, $targets, $mappings);
- If the mapping specifies a callback method, use the callback instead of
- setTargetElement().
- */
- self::loadMappers();
- foreach ($this->config['mappings'] as $mapping) {
- // Retrieve source element's value from parser.
- if (isset($sources[$this->id][$mapping['source']]) &&
- is_array($sources[$this->id][$mapping['source']]) &&
- isset($sources[$this->id][$mapping['source']]['callback']) &&
- function_exists($sources[$this->id][$mapping['source']]['callback'])) {
- $callback = $sources[$this->id][$mapping['source']]['callback'];
- $value = $callback($source, $result, $mapping['source']);
- }
- else {
- $value = $parser->getSourceElement($source, $result, $mapping['source']);
+ // This is where the actual mapping happens: For every mapping we invoke
+ // the parser's getSourceElement() method to retrieve the value of the
+ // source element and pass it to the processor's setTargetElement() to stick
+ // it on the right place of the target item.
+ foreach ($mappings as $mapping) {
+
+ // Ensure there's always a language set.
+ if (empty($mapping['language'])) {
+ $mapping['language'] = LANGUAGE_NONE;
}
- // Map the source element's value to the target.
- if (isset($targets[$this->id][$mapping['target']]) &&
- is_array($targets[$this->id][$mapping['target']]) &&
- isset($targets[$this->id][$mapping['target']]['callback']) &&
- function_exists($targets[$this->id][$mapping['target']]['callback'])) {
- $callback = $targets[$this->id][$mapping['target']]['callback'];
+ $value = $this->getSourceValue($source, $result, $mapping['source']);
- // All target callbacks expect an array.
- if (!is_array($value)) {
- $value = array($value);
- }
+ $this->mapToTarget($source, $mapping['target'], $target_item, $value, $mapping);
+ }
+
+ // Invoke postprocess callbacks on targets.
+ $this->invokeProcessCallbacks('post', $source, $target_item, $targets, $mappings);
+
+ return $target_item;
+ }
+
+ protected function invokeProcessCallbacks($key, FeedsSource $source, $target_item, array $targets, array &$mappings) {
+ $key .= 'process_callbacks';
+
+ foreach (array_keys($mappings) as $delta) {
+ $target = $mappings[$delta]['target'];
- $callback($source, $target_item, $mapping['target'], $value, $mapping);
+ if (!isset($targets[$target][$key])) {
+ continue;
}
- else {
- $this->setTargetElement($source, $target_item, $mapping['target'], $value, $mapping);
+
+ foreach ($targets[$target][$key] as $callback) {
+ $callback($source, $target_item, $targets[$target], $mappings[$delta]);
}
}
+ }
- return $target_item;
+ /**
+ * Returns the values from the parser, or callback.
+ *
+ * @param FeedsSource $source
+ * The feed source.
+ * @param FeedsParserResult $result
+ * The parser result.
+ * @param string $source_key
+ * The current key being processed.
+ *
+ * @return mixed
+ * A value, or a list of values.
+ */
+ protected function getSourceValue(FeedsSource $source, FeedsParserResult $result, $source_key) {
+ $sources = $this->getCachedSources();
+
+ if (isset($sources[$source_key]['callback'])) {
+ $callback = $sources[$source_key]['callback'];
+
+ return $callback($source, $result, $source_key);
+ }
+
+ return feeds_importer($this->id)->parser->getSourceElement($source, $result, $source_key);
+ }
+
+ /**
+ * Maps values onto the target item.
+ *
+ * @param FeedsSource $source
+ * The feed source.
+ * @param mixed &$target_item
+ * The target item to apply values into.
+ * @param mixed $value
+ * A value, or a list of values.
+ * @param array $mapping
+ * The mapping configuration.
+ */
+ protected function mapToTarget(FeedsSource $source, $target, &$target_item, $value, array $mapping) {
+ $targets = $this->getCachedTargets();
+
+ // Map the source element's value to the target.
+ // If the mapping specifies a callback method, use the callback instead of
+ // setTargetElement().
+ if (isset($targets[$target]['callback'])) {
+
+ $callback = $targets[$target]['callback'];
+
+ // All target callbacks expect an array.
+ if (!is_array($value)) {
+ $value = array($value);
+ }
+
+ $callback($source, $target_item, $target, $value, $mapping);
+ }
+
+ else {
+ $this->setTargetElement($source, $target_item, $target, $value, $mapping);
+ }
}
/**
@@ -705,6 +792,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
'input_format' => NULL,
'skip_hash_check' => FALSE,
'bundle' => $bundle,
+ 'language' => LANGUAGE_NONE,
);
}
@@ -731,6 +819,16 @@ abstract class FeedsProcessor extends FeedsPlugin {
);
}
+ if (module_exists('locale') && !empty($info['entity keys']['language'])) {
+ $form['language'] = array(
+ '#type' => 'select',
+ '#options' => array(LANGUAGE_NONE => t('Language neutral')) + locale_language_list('name'),
+ '#title' => t('Language'),
+ '#required' => TRUE,
+ '#default_value' => $this->config['language'],
+ );
+ }
+
$tokens = array('@entities' => strtolower($info['label plural']));
$form['update_existing'] = array(
@@ -822,6 +920,22 @@ abstract class FeedsProcessor extends FeedsPlugin {
}
/**
+ * Allows other modules to expose targets.
+ *
+ * @param array &$targets
+ * The existing target array.
+ */
+ protected function getHookTargets(array &$targets) {
+ self::loadMappers();
+
+ $entity_type = $this->entityType();
+ $bundle = $this->bundle();
+ $targets += module_invoke_all('feeds_processor_targets', $entity_type, $bundle);
+
+ drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle);
+ }
+
+ /**
* Set a concrete target element. Invoked from FeedsProcessor::map().
*
* @ingroup mappingapi
@@ -832,6 +946,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
case 'guid':
$target_item->feeds_item->$target_element = $value;
break;
+
default:
$target_item->$target_element = $value;
break;
@@ -852,10 +967,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
* The serial id of an entity if found, 0 otherwise.
*/
protected function existingEntityId(FeedsSource $source, FeedsParserResult $result) {
- $targets = &drupal_static('FeedsProcessor::existingEntityId', array());
- if (!isset($targets[$this->id])) {
- $targets[$this->id] = $this->getMappingTargets();
- }
+ $targets = $this->getCachedTargets();
$entity_id = 0;
@@ -873,13 +985,13 @@ abstract class FeedsProcessor extends FeedsPlugin {
->fetchField();
}
- if (!$entity_id && !empty($targets[$this->id][$target]['unique_callbacks'])) {
+ if (!$entity_id && !empty($targets[$target]['unique_callbacks'])) {
if (!is_array($value)) {
$value = array($value);
}
- foreach ($targets[$this->id][$target]['unique_callbacks'] as $callback) {
- if (is_callable($callback) && $entity_id = call_user_func_array($callback, array($source, $this->entityType(), $this->bundle(), $target, $value))) {
+ foreach ($targets[$target]['unique_callbacks'] as $callback) {
+ if ($entity_id = $callback($source, $this->entityType(), $this->bundle(), $target, $value)) {
// Stop at the first unique ID returned by a callback.
break;
}
@@ -1010,7 +1122,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
$message .= '
' . drupal_var_export($item). ''; $message .= '
' . drupal_var_export($entity) . ''; + // $message .= '
' . drupal_var_export($entity) . ''; return $message; } diff --git a/plugins/FeedsTermProcessor.inc b/plugins/FeedsTermProcessor.inc index 40b9207..ca3dd3d 100644 --- a/plugins/FeedsTermProcessor.inc +++ b/plugins/FeedsTermProcessor.inc @@ -38,10 +38,24 @@ class FeedsTermProcessor extends FeedsProcessor { } /** + * {@inheritdoc} + */ + protected function entityLoad(FeedsSource $source, $entity_id) { + if ($term = parent::entityLoad($source, $entity_id)) { + $vocabulary = $this->vocabulary(); + $term->vid = $vocabulary->vid; + $term->vocabulary_machine_name = $vocabulary->machine_name; + return $term; + } + + return FALSE; + } + + /** * Validates a term. */ protected function entityValidate($term) { - if (drupal_strlen($term->name) == 0) { + if (!strlen($term->name)) { throw new FeedsValidationException(t('Term name missing.')); } } @@ -177,21 +191,13 @@ class FeedsTermProcessor extends FeedsProcessor { 'description' => array( 'name' => t('Term description'), 'description' => t('Description of the taxonomy term.'), - 'summary_callback' => 'text_feeds_summary_callback', - 'form_callback' => 'text_feeds_form_callback', + 'summary_callbacks' => array('text_feeds_summary_callback'), + 'form_callbacks' => array('text_feeds_form_callback'), ), ); - // Let implementers of hook_feeds_term_processor_targets() add their targets. - try { - self::loadMappers(); - $entity_type = $this->entityType(); - $bundle = $this->bundle(); - drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle); - } - catch (Exception $e) { - // Do nothing. - } + $this->getHookTargets($targets); + return $targets; } diff --git a/plugins/FeedsUserProcessor.inc b/plugins/FeedsUserProcessor.inc index d01db43..02607f2 100644 --- a/plugins/FeedsUserProcessor.inc +++ b/plugins/FeedsUserProcessor.inc @@ -193,11 +193,7 @@ class FeedsUserProcessor extends FeedsProcessor { ); } - // Let other modules expose mapping targets. - self::loadMappers(); - $entity_type = $this->entityType(); - $bundle = $this->bundle(); - drupal_alter('feeds_processor_targets', $targets, $entity_type, $bundle); + $this->getHookTargets($targets); return $targets; } diff --git a/tests/feeds_mapper_hooks.test b/tests/feeds_mapper_hooks.test new file mode 100644 index 0000000..5d76043 --- /dev/null +++ b/tests/feeds_mapper_hooks.test @@ -0,0 +1,72 @@ + 'Mapper: Hooks and callbacks', + 'description' => 'Test case for the various callbacks implemented for mappers.', + 'group' => 'Feeds', + ); + } + + /** + * Basic test loading a double entry CSV file. + */ + public function test() { + + // Create and configure importer. + $this->createImporterConfiguration(); + $this->addMappings('syndication', array( + 0 => array( + 'source' => 'title', + 'target' => 'title', + ), + 1 => array( + 'source' => 'description', + 'target' => 'test_target', + ), + )); + + // Checks that alter hooks are invoked. + $this->assertText(t('The target description was altered.')); + + // Inherently tests preprocess callbacks. + // @see feeds_tests_mapper_set_target() + $nid = $this->createFeedNode(); + $this->drupalGet('node/2/edit'); + $body_value = $this->xpath('//*[@name = "body[und][0][value]"]'); + $value = unserialize((string) $body_value[0]); + $this->assertTrue(!empty($value)); + + // Tests old-style target keys. + $this->addMappings('syndication', array( + 2 => array( + 'source' => 'url', + 'target' => 'test_target_compat', + ), + )); + + // Click gear to get form. + $this->drupalPostAJAX(NULL, array(), 'mapping_settings_edit_2'); + + // Set some settings. + $edit = array( + 'config[2][settings][checkbox]' => 1, + 'config[2][settings][textfield]' => 'Some text', + 'config[2][settings][textarea]' => 'Textarea value: Didery dofffffffffffffffffffffffffffffffffffff', + 'config[2][settings][radios]' => 'option1', + 'config[2][settings][select]' => 'option4', + ); + $this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_2'); + $this->assertText(t('* Changes made to target configuration are stored temporarily. Click Save to make your changes permanent.')); + } + +} diff --git a/tests/feeds_mapper_taxonomy.test b/tests/feeds_mapper_taxonomy.test index 4a2eb8a..801b2d0 100644 --- a/tests/feeds_mapper_taxonomy.test +++ b/tests/feeds_mapper_taxonomy.test @@ -234,13 +234,16 @@ class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase { $target = 'field_tags'; $mapping = array( 'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_ID, + 'language' => LANGUAGE_NONE, ); - taxonomy_feeds_set_target(NULL, $entity, $target, $terms, $mapping); + $source = FeedsSource::instance('tmp', 0); + + taxonomy_feeds_set_target($source, $entity, $target, $terms, $mapping); $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10); // Test a second mapping with a bogus term id. - taxonomy_feeds_set_target(NULL, $entity, $target, array(1234), $mapping); + taxonomy_feeds_set_target($source, $entity, $target, array(1234), $mapping); $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10); } @@ -282,16 +285,19 @@ class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase { $target = 'field_tags'; $mapping = array( 'term_search' => FEEDS_TAXONOMY_SEARCH_TERM_GUID, + 'language' => LANGUAGE_NONE, ); - taxonomy_feeds_set_target(NULL, $entity, $target, $guids, $mapping); + $source = FeedsSource::instance('tmp', 0); + + taxonomy_feeds_set_target($source, $entity, $target, $guids, $mapping); $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10); foreach ($entity->field_tags[LANGUAGE_NONE] as $delta => $values) { $this->assertEqual($tids[$delta], $values['tid'], 'Correct term id foud.'); } // Test a second mapping with a bogus term id. - taxonomy_feeds_set_target(NULL, $entity, $target, array(1234), $mapping); + taxonomy_feeds_set_target($source, $entity, $target, array(1234), $mapping); $this->assertEqual(count($entity->field_tags[LANGUAGE_NONE]), 10); foreach ($entity->field_tags[LANGUAGE_NONE] as $delta => $values) { $this->assertEqual($tids[$delta], $values['tid'], 'Correct term id foud.'); diff --git a/tests/feeds_tests.module b/tests/feeds_tests.module index fe25704..d6aec41 100644 --- a/tests/feeds_tests.module +++ b/tests/feeds_tests.module @@ -102,15 +102,26 @@ function feeds_tests_files_remote() { } /** - * Implements hook_feeds_processor_targets_alter(). + * Implements hook_feeds_processor_targets(). */ -function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { +function feeds_tests_feeds_processor_targets($entity_type, $bundle) { + $targets = array(); + + // Tests that old keys still work. + $targets['test_target_compat'] = array( + 'name' => t('Old style target'), + 'callback' => 'feeds_tests_mapper_set_target', + 'summary_callback' => 'feeds_tests_mapper_summary', + 'form_callback' => 'feeds_tests_mapper_form', + ); + $targets['test_target'] = array( 'name' => t('Test Target'), 'description' => t('This is a test target.'), 'callback' => 'feeds_tests_mapper_set_target', - 'summary_callback' => 'feeds_tests_mapper_summary', - 'form_callback' => 'feeds_tests_mapper_form', + 'summary_callbacks' => array('feeds_tests_mapper_summary'), + 'form_callbacks' => array('feeds_tests_mapper_form'), + 'preprocess_callbacks' => array('feeds_tests_preprocess_callback'), ); $targets['test_unique_target'] = array( @@ -119,7 +130,30 @@ function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bun 'callback' => 'feeds_tests_mapper_set_target', 'optional_unique' => TRUE, 'unique_callbacks' => array('feeds_tests_mapper_unique'), + 'preprocess_callbacks' => array('feeds_tests_preprocess_callback'), ); + + return $targets; +} + +/** + * Implements hook_feeds_processor_targets_alter(). + */ +function feeds_tests_feeds_processor_targets_alter(array &$targets, $entity_type, $bundle) { + if (!isset($targets['test_target'])) { + return; + } + + $targets['test_target']['description'] = t('The target description was altered.'); +} + +/** + * Preprocess callback for test_target. + * + * @see feeds_tests_feeds_processor_targets() + */ +function feeds_tests_preprocess_callback(FeedsSource $source, $target_item, array $target, array &$mapping) { + $mapping['required_value'] = TRUE; } /** @@ -127,7 +161,11 @@ function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bun * * @see my_module_set_target() */ -function feeds_tests_mapper_set_target($source, $entity, $target, $value, $mapping) { +function feeds_tests_mapper_set_target(FeedsSource $source, $entity, $target, array $values, array $mapping) { + if (empty($mapping['required_value'])) { + trigger_error('The required value was not set.', E_USER_ERROR); + } + $entity->body['und'][0]['value'] = serialize($mapping); } @@ -136,7 +174,7 @@ function feeds_tests_mapper_set_target($source, $entity, $target, $value, $mappi * * @see my_module_summary_callback() */ -function feeds_tests_mapper_summary($mapping, $target, $form, $form_state) { +function feeds_tests_mapper_summary(array $mapping, $target, array $form, array $form_state) { $options = array( 'option1' => t('Option 1'), 'option2' => t('Another Option'), @@ -174,7 +212,7 @@ function feeds_tests_mapper_summary($mapping, $target, $form, $form_state) { /** * Provides the form with mapper settings. */ -function feeds_tests_mapper_form($mapping, $target, $form, $form_state) { +function feeds_tests_mapper_form(array $mapping, $target, array $form, array $form_state) { $mapping += array( 'checkbox' => FALSE, 'textfield' => '', @@ -217,7 +255,7 @@ function feeds_tests_mapper_form($mapping, $target, $form, $form_state) { /** * Callback for unique_callbacks for test_target mapper. * - * @see feeds_tests_feeds_processor_targets_alter() + * @see feeds_tests_feeds_processor_targets() */ function feeds_tests_mapper_unique(FeedsSource $source, $entity_type, $bundle, $target, array $values) { $query = new EntityFieldQuery();