diff --git a/feeds.api.php b/feeds.api.php index e0dfc12..19e7bb8 100644 --- a/feeds.api.php +++ b/feeds.api.php @@ -278,6 +278,11 @@ function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam // configuration form. 'summary_callback' => 'my_module_summary_callback', 'form_callback' => 'my_module_form_callback', + + // Specify post-process callback. This runs after all values have been + // mapped to the target. + // @see feeds_filter_empty_field_items() + 'post_process' => 'my_module_post_process_callback', ); $targets['my_node_field2'] = array( 'name' => t('My Second custom node field'), @@ -303,8 +308,9 @@ function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam * Associative array of the mapping settings from the per mapping * configuration form. */ -function my_module_set_target($source, $entity, $target, $value, $mapping) { - $entity->{$target}[$entity->language][0]['value'] = $value; +function my_module_set_target($source, $entity, $target, array $values, $mapping) { + $entity->{$target}[$entity->language][0]['value'] = reset($values); + if (isset($source->importer->processor->config['input_format'])) { $entity->{$target}[$entity->language][0]['format'] = $source->importer->processor->config['input_format']; @@ -312,6 +318,24 @@ function my_module_set_target($source, $entity, $target, $value, $mapping) { } /** + * Example post-process callback. + * + * @param FeedsSource $source + * The feed source. + * @param object $entity + * The entity being created or updated. + * @param string $target + * The target being mapped. + * @param array $target_info + * The target info array. + */ +function my_module_post_process_callback(FeedsSource $source, $entity, $target, array $target_info) { + foreach ($entity->{$target}['und'] as $delta => $value) { + $entity->{$target}['und'][$delta] = drupal_strtolower($value); + } +} + +/** * Example of the summary_callback specified in * hook_feeds_processor_targets_alter(). * diff --git a/feeds.module b/feeds.module index f581d3f..7a115f1 100644 --- a/feeds.module +++ b/feeds.module @@ -1249,3 +1249,28 @@ function feeds_api_version() { $version = feeds_ctools_plugin_api('feeds', 'plugins'); return $version['version']; } + +/** + * Filters empty field values. + * + * This is used as a post-process callback for field targets. + * + * @param FeedsSource $source + * The feed source. + * @param object $entity + * The entity being created or updated. + * @param string $target + * The target being mapped. + * @param array $target_info + * The target info array. + */ +function feeds_filter_empty_field_items(FeedsSource $source, $entity, $target, array $target_info) { + $field_name = $target; + if (!empty($target_info['real_target'])) { + $field_name = $target_info['real_target']; + } + + $field = field_info_field($field_name); + + $entity->{$field_name}['und'] = _field_filter_items($field, $entity->{$field_name}['und']); +} diff --git a/mappers/date.inc b/mappers/date.inc index 8ff4cbb..081ae88 100644 --- a/mappers/date.inc +++ b/mappers/date.inc @@ -21,6 +21,7 @@ function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam 'callback' => 'date_feeds_set_target', 'description' => t('The start date for the @name field. Also use if mapping both start and end.', array('@name' => $instance['label'])), 'real_target' => $name, + 'post_process' => array('feeds_filter_empty_field_items'), ); $targets[$name . ':end'] = array( 'name' => t('@name: End', array('@name' => $instance['label'])), @@ -33,41 +34,28 @@ function date_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam } /** - * Implements hook_feeds_set_target(). - * - * @param $node - * The target node. - * @param $field_name - * The name of field on the target node to map to. - * @param $feed_element - * The value to be mapped. Should be either a (flexible) date string - * or a FeedsDateTimeElement object. - * + * Callback for setting target values. */ -function date_feeds_set_target($source, $entity, $target, $feed_element) { +function date_feeds_set_target($source, $entity, $target, array $values) { list($field_name, $sub_field) = explode(':', $target, 2); - if (!is_array($feed_element)) { - $feed_element = array($feed_element); - } - $delta = 0; - foreach ($feed_element as $f) { + foreach ($values as $value) { - if (!($feed_element instanceof FeedsDateTimeElement)) { + if (!($value instanceof FeedsDateTimeElement)) { - if (empty($f) || !is_numeric($f) && is_string($f) && !date_create($f)) { - $f = new FeedsDateTimeElement(NULL, NULL); + if (empty($value) || !is_numeric($value) && is_string($value) && !date_create($value)) { + $value = new FeedsDateTimeElement(NULL, NULL); } elseif ($sub_field == 'end') { - $f = new FeedsDateTimeElement(NULL, $f); + $value = new FeedsDateTimeElement(NULL, $value); } else { - $f = new FeedsDateTimeElement($f, NULL); + $value = new FeedsDateTimeElement($value, NULL); } } - $f->buildDateField($entity, $field_name, $delta); + $value->buildDateField($entity, $field_name, $delta); $delta++; } } diff --git a/mappers/file.inc b/mappers/file.inc index aa967b9..ca4b5bb 100644 --- a/mappers/file.inc +++ b/mappers/file.inc @@ -21,6 +21,7 @@ function file_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam 'callback' => 'file_feeds_set_target', 'description' => t('The URI of the @label field.', array('@label' => $instance['label'])), 'real_target' => $name, + 'post_process' => array('feeds_filter_empty_field_items'), ); if ($info['type'] == 'image') { @@ -48,57 +49,45 @@ function file_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam * user has decided to map to and $value contains the value of the feed item * element the user has picked as a source. */ -function file_feeds_set_target($source, $entity, $target, $value) { - if (empty($value)) { - return; - } - - // Make sure $value is an array of objects of type FeedsEnclosure. - if (!is_array($value)) { - $value = array($value); - } - +function file_feeds_set_target($source, $entity, $target, array $values) { // Add default of uri for backwards compatibility. list($field_name, $sub_field) = explode(':', $target . ':uri'); $info = field_info_field($field_name); if ($sub_field == 'uri') { - foreach ($value as $k => $v) { + foreach ($values as $k => $v) { if (!($v instanceof FeedsEnclosure)) { if (is_string($v)) { - $value[$k] = new FeedsEnclosure($v, file_get_mimetype($v)); + $values[$k] = new FeedsEnclosure($v, file_get_mimetype($v)); } else { - unset($value[$k]); + // Set the value for FALSE rather than remove it to keep our deltas + // correct. + $values[$k] = FALSE; } } } - if (empty($value)) { - return; - } - static $destination; - if (!$destination) { - $entity_type = $source->importer->processor->entityType(); - $bundle = $source->importer->processor->bundle(); + $entity_type = $source->importer->processor->entityType(); + $bundle = $source->importer->processor->bundle(); - $instance_info = field_info_instance($entity_type, $field_name, $bundle); + $instance_info = field_info_instance($entity_type, $field_name, $bundle); - // Determine file destination. - // @todo This needs review and debugging. - $data = array(); - if (!empty($entity->uid)) { - $data[$entity_type] = $entity; - } - $destination = file_field_widget_uri($info, $instance_info, $data); + // Determine file destination. + // @todo This needs review and debugging. + $data = array(); + if (!empty($entity->uid)) { + $data[$entity_type] = $entity; } + + $destination = file_field_widget_uri($info, $instance_info, $data); } // Populate entity. $field = isset($entity->$field_name) ? $entity->$field_name : array(LANGUAGE_NONE => array()); $delta = 0; - foreach ($value as $v) { + foreach ($values as $value) { if ($info['cardinality'] == $delta) { break; } @@ -110,18 +99,20 @@ function file_feeds_set_target($source, $entity, $target, $value) { switch ($sub_field) { case 'alt': case 'title': - $field[LANGUAGE_NONE][$delta][$sub_field] = $v; + $field[LANGUAGE_NONE][$delta][$sub_field] = $value; break; case 'uri': - try { - $file = $v->getFile($destination); - $field[LANGUAGE_NONE][$delta] += (array) $file; - // @todo: Figure out how to properly populate this field. - $field[LANGUAGE_NONE][$delta]['display'] = 1; - } - catch (Exception $e) { - watchdog_exception('Feeds', $e, nl2br(check_plain($e))); + if ($value) { + try { + $file = $value->getFile($destination); + $field[LANGUAGE_NONE][$delta] += (array) $file; + // @todo: Figure out how to properly populate this field. + $field[LANGUAGE_NONE][$delta]['display'] = 1; + } + catch (Exception $e) { + watchdog_exception('Feeds', $e, nl2br(check_plain($e))); + } } break; } diff --git a/mappers/link.inc b/mappers/link.inc index 45a86f7..8d324e1 100644 --- a/mappers/link.inc +++ b/mappers/link.inc @@ -20,6 +20,7 @@ function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam 'callback' => 'link_feeds_set_target', 'description' => t('The @label field of the entity.', array('@label' => $instance['label'])), 'real_target' => $name, + 'post_process' => array('feeds_filter_empty_field_items'), ); } if (array_key_exists('title', $info['columns'])) { @@ -30,6 +31,10 @@ function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam 'real_target' => $name, ); } + // Only set the post_process for one column. + if (!isset($targets[$name . ':url']) && isset($targets[$name . ':title'])) { + $targets[$name . ':title']['post_process'] = 'feeds_filter_empty_field_items'; + } } } } @@ -41,36 +46,24 @@ function link_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam * user has decided to map to and $value contains the value of the feed item * element the user has picked as a source. */ -function link_feeds_set_target($source, $entity, $target, $value) { - if (empty($value)) { - return; - } - - // Handle non-multiple value fields. - if (!is_array($value)) { - $value = array($value); - } +function link_feeds_set_target($source, $entity, $target, array $values) { // Iterate over all values. list($field_name, $column) = explode(':', $target); - $info = field_info_field($field_name); $field = isset($entity->$field_name) ? $entity->$field_name : array(); $delta = 0; - foreach ($value as $v) { - if ($info['cardinality'] == $delta) { - break; + foreach ($values as $value) { + if (is_object($value) && ($value instanceof FeedsElement)) { + $value = $value->getValue(); } - if (is_object($v) && ($v instanceof FeedsElement)) { - $v = $v->getValue(); - } - - if (is_scalar($v)) { - $field['und'][$delta][$column] = $v; - $delta++; + if (is_scalar($value)) { + $field['und'][$delta][$column] = $value; } + $delta++; } + $entity->$field_name = $field; } diff --git a/mappers/number.inc b/mappers/number.inc index b64c4ee..e93bd0b 100644 --- a/mappers/number.inc +++ b/mappers/number.inc @@ -27,6 +27,7 @@ function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_n 'name' => check_plain($instance['label']), 'callback' => 'number_feeds_set_target', 'description' => t('The @label field of the entity.', array('@label' => $instance['label'])), + 'post_process' => array('feeds_filter_empty_field_items'), ); } } @@ -37,36 +38,18 @@ function number_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_n * * Ensure that $value is a numeric to avoid database errors. */ -function number_feeds_set_target($source, $entity, $target, $value) { - - // Do not perform the regular empty() check here. 0 is a valid value. That's - // really just a performance thing anyway. - - if (!is_array($value)) { - $value = array($value); - } - - $info = field_info_field($target); - +function number_feeds_set_target($source, $entity, $target, array $values) { // Iterate over all values. $field = isset($entity->$target) ? $entity->$target : array('und' => array()); - // Allow for multiple mappings to the same target. - $delta = count($field['und']); - - foreach ($value as $v) { - - if ($info['cardinality'] == $delta) { - break; - } + foreach ($values as $value) { - if (is_object($v) && ($v instanceof FeedsElement)) { - $v = $v->getValue(); + if (is_object($value) && ($value instanceof FeedsElement)) { + $value = $value->getValue(); } - if (is_numeric($v)) { - $field['und'][$delta]['value'] = $v; - $delta++; + if (is_numeric($value)) { + $field['und'][] = array('value' => $value); } } diff --git a/mappers/path.inc b/mappers/path.inc index 47ae0fc..82c149e 100644 --- a/mappers/path.inc +++ b/mappers/path.inc @@ -33,14 +33,17 @@ function path_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam * user has decided to map to and $value contains the value of the feed item * element the user has picked as a source. */ -function path_feeds_set_target($source, $entity, $target, $value, $mapping) { - if (empty($value)) { - $value = ''; - } +function path_feeds_set_target($source, $entity, $target, array $values, $mapping) { + + $alias = FALSE; - // Path alias cannot be multi-valued, so use the first value. - if (is_array($value)) { - $value = $value[0]; + // Path alias cannot be multi-valued, so use the first non-empty value. + foreach ($values as $value) { + $value = ltrim(trim($value), '/'); + if (strlen($value)) { + $alias = $value; + break; + } } $entity->path = array(); @@ -58,15 +61,11 @@ function path_feeds_set_target($source, $entity, $target, $value, $mapping) { } } - $entity->path['pathauto'] = FALSE; - // Allow pathauto to set the path alias if the option is set, and this value - // is empty. - if (!empty($mapping['pathauto_override']) && !$value) { - $entity->path['pathauto'] = TRUE; - } - else { - $entity->path['alias'] = ltrim($value, '/'); - } + // Allow pathauto to set the path alias if the option is set, and the value is + // empty. + $entity->path['pathauto'] = !empty($mapping['pathauto_override']) && $alias === FALSE; + + $entity->path['alias'] = (string) $alias; } /** diff --git a/mappers/profile.inc b/mappers/profile.inc index 0dd62de..00fdf3a 100644 --- a/mappers/profile.inc +++ b/mappers/profile.inc @@ -30,6 +30,6 @@ function profile_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_ /** * Set the user profile target after import. */ -function profile_feeds_set_target($source, $entity, $target, $value, $mapping) { - $entity->$target = $value; +function profile_feeds_set_target($source, $entity, $target, array $values, $mapping) { + $entity->$target = reset($values); } diff --git a/mappers/taxonomy.inc b/mappers/taxonomy.inc index 02ac54e..4875a98 100644 --- a/mappers/taxonomy.inc +++ b/mappers/taxonomy.inc @@ -67,6 +67,7 @@ function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle 'description' => t('The @label field of the entity.', array('@label' => $instance['label'])), 'summary_callback' => 'taxonomy_feeds_summary_callback', 'form_callback' => 'taxonomy_feeds_form_callback', + 'post_process' => array('feeds_filter_empty_field_items'), ); } } @@ -77,17 +78,7 @@ function taxonomy_feeds_processor_targets_alter(&$targets, $entity_type, $bundle * * @todo Do not create new terms for non-autotag fields. */ -function taxonomy_feeds_set_target($source, $entity, $target, $terms, $mapping = array()) { - - // Allow mapping the string '0' to a term name. - if (empty($terms) && $terms != 0) { - return; - } - - // Handle non-multiple values. - if (!is_array($terms)) { - $terms = array($terms); - } +function taxonomy_feeds_set_target($source, $entity, $target, array $terms, $mapping = array()) { // Add in default values. $mapping += array( @@ -123,7 +114,6 @@ function taxonomy_feeds_set_target($source, $entity, $target, $terms, $mapping = // Iterate over all values. foreach ($terms as $term) { - if ($info['cardinality'] == $delta) { break; } @@ -171,7 +161,7 @@ function taxonomy_feeds_set_target($source, $entity, $target, $terms, $mapping = } if ($tid && isset($cache['allowed_values'][$target][$tid])) { - $field['und'][$delta]['tid'] = $tid; + $field['und'][] = array('tid' => $tid); $delta++; } } diff --git a/mappers/text.inc b/mappers/text.inc index 48447d7..ead69b1 100644 --- a/mappers/text.inc +++ b/mappers/text.inc @@ -25,6 +25,7 @@ function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam 'name' => check_plain($instance['label']), 'callback' => 'text_feeds_set_target', 'description' => t('The @label field of the entity.', array('@label' => $instance['label'])), + 'post_process' => array('feeds_filter_empty_field_items'), ); } } @@ -33,45 +34,27 @@ function text_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam /** * Callback for mapping text fields. */ -function text_feeds_set_target($source, $entity, $target, $value) { - if (empty($value)) { - return; - } - - if (!is_array($value)) { - $value = array($value); - } +function text_feeds_set_target($source, $entity, $target, array $values) { if (isset($source->importer->processor->config['input_format'])) { $format = $source->importer->processor->config['input_format']; } - $info = field_info_field($target); - // Iterate over all values. $field = isset($entity->$target) ? $entity->$target : array('und' => array()); - // Allow for multiple mappings to the same target. - $delta = count($field['und']); - - foreach ($value as $v) { + foreach ($values as $value) { - if ($info['cardinality'] == $delta) { - break; + if (is_object($value) && ($value instanceof FeedsElement)) { + $value = $value->getValue(); } - if (is_object($v) && ($v instanceof FeedsElement)) { - $v = $v->getValue(); - } - - if (is_scalar($v)) { - $field['und'][$delta]['value'] = $v; - + if (is_scalar($value)) { + $value = array('value' => (string) $value); if (isset($format)) { - $field['und'][$delta]['format'] = $format; + $value['format'] = $format; } - - $delta++; + $field['und'][] = $value; } } diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index c2b0af5..600709d 100755 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -506,12 +506,32 @@ abstract class FeedsProcessor extends FeedsPlugin { isset($targets[$this->id][$mapping['target']]['callback']) && function_exists($targets[$this->id][$mapping['target']]['callback'])) { $callback = $targets[$this->id][$mapping['target']]['callback']; + + // All target callbacks expect an array. + if (!is_array($value)) { + $value = array($value); + } $callback($source, $target_item, $mapping['target'], $value, $mapping); } else { $this->setTargetElement($source, $target_item, $mapping['target'], $value, $mapping); } } + + foreach ($this->config['mappings'] as $mapping) { + $target = $mapping['target']; + + if (isset($targets[$this->id][$target]) && + is_array($targets[$this->id][$target]) && + isset($targets[$this->id][$target]['post_process']) && + is_array($targets[$this->id][$target]['post_process'])) { + + foreach ($targets[$this->id][$target]['post_process'] as $callback) { + $callback($source, $target_item, $target, $targets[$this->id][$target], $mapping); + } + } + } + return $target_item; } diff --git a/tests/feeds/nodes_multiple_body.csv b/tests/feeds/nodes_multiple_body.csv new file mode 100644 index 0000000..4029f90 --- /dev/null +++ b/tests/feeds/nodes_multiple_body.csv @@ -0,0 +1,4 @@ +Title,Body,Body2,Body3 +"Ut wisi enim ad minim veniam",, Body text 2,"Body 3" +"Duis autem vel eum iriure dolor",, Body text 2,"Body 3" +"Nam liber tempor",, Body text 2,"Body 3" diff --git a/tests/feeds_mapper_field.test b/tests/feeds_mapper_field.test index 2506a29..5e004c9 100644 --- a/tests/feeds_mapper_field.test +++ b/tests/feeds_mapper_field.test @@ -87,4 +87,49 @@ class FeedsMapperFieldTestCase extends FeedsMapperTestCase { $this->assertNodeFieldValue('gamma', '1.20'); $this->assertNodeFieldValue('delta', '5.62951'); } + + /** + * Tests post_process callbacks. + * + * @see feeds_filter_empty_field_items() + */ + public function testFilterEmptyValues() { + // Create and configure importer. + $this->createImporterConfiguration('Content CSV', 'csv'); + $this->setSettings('csv', NULL, array('content_type' => '', 'import_period' => FEEDS_SCHEDULE_NEVER)); + $this->setPlugin('csv', 'FeedsFileFetcher'); + $this->setPlugin('csv', 'FeedsCSVParser'); + $this->setSettings('csv', 'FeedsNodeProcessor', array('bundle' => 'article')); + + // Map multiple values to the same field. The first value is empty, and the + // third should be removed since cardinality = 1. + $this->addMappings('csv', array( + 0 => array( + 'source' => 'title', + 'target' => 'title', + ), + 1 => array( + 'source' => 'body', + 'target' => 'body', + ), + 2 => array( + 'source' => 'body2', + 'target' => 'body', + ), + 3 => array( + 'source' => 'body3', + 'target' => 'body', + ), + )); + + // Import CSV file. + $this->importFile('csv', $this->absolutePath() . '/tests/feeds/nodes_multiple_body.csv'); + $this->assertText(t('Created 3 nodes.')); + foreach (range(1, 3) as $nid) { + $this->drupalGet("node/$nid"); + $this->assertText('Body text 2'); + $this->assertNoText('Body 3'); + } + } + }