Problem/Motivation

Given a content type with a field of type text_with_summary (e.g.: the body field, which Drupal adds by default) If I map a source field to the summary field (e.g.: body:summary), but I do not also map a source field to the main part of the field (e.g.: body), then the main part of the field is erased upon import.

Steps to reproduce

  1. Download the latest version of Drupal 7 core, the latest version of the Chaos tools module (ctools-7.x-1.11 at time of writing), and the latest version of the Job Scheduler module (job_scheduler-7.x-2.0-alpha3 at time of writing). Clone the Feeds module, branch 7.x-2.x.
  2. Install Drupal using the minimal install profile. Enable Field UI, Feeds, Feeds UI, and their dependencies.
  3. Go to /admin/structure/types and create a content type. I named mine "Test content type" (test_content_type) and accepted all the default options.
  4. Note that Drupal automatically adds a body field to the new content type.
  5. Go to /node/add and create a new node of the new content type. Enter something distinct into the Title, Summary, and Body fields. I used Lorem for the Title, Ipsum for the Summary, and Dolor for the Body. Take note of the node ID (mine had a node ID of 1).
  6. Go to /admin/structure/feeds and create a Feeds importer. I named mine "Test importer" (test_importer), and used the following options:
    1. Basic settings:
      • Attach to content type: Use standalone form
      • Periodic import: Off
      • Import on submission: Checked
      • Process in background: Unchecked
    2. Fetcher: File upload
      • Allowed file extensions: txt csv tsv xml opml
      • Immediately delete uploaded file after import: Unchecked
      • Supply path to file or directory directly: Unchecked
      • Upload directory: private://feeds
    3. Parser: CSV parser
      • Default delimiter: ,
      • No headers: Unchecked
      • File encoding: UTF-8
    4. Processor: Node
      Settings:
      • Bundle: Test content type
      • Insert new nodes: Insert new nodes
      • Update existing nodes: Update existing nodes
      • Skip hash check: Checked
      • Text format: Plain text
      • Action to take when previously imported nodes are missing in the feed: Skip non-existent nodes
      • Author: anonymous
      • Authorize: Unchecked
      • Expire nodes: Never

      Mapping:

      Source Target Target configuration
      nid Node ID (nid) Used as unique.
      Title Title (title) Not used as unique.
      Summary Body: Summary (body:summary)  
      Body Body (body) Text format: Plain text
  7. Create a CSV file on your local machine. The header row must match the Sources in the Mapping you set up earlier. The nid field must match the node ID you took note of earlier when you created the node. The rest of the data should be different from the data you entered when you created the node. For example, my CSV file was:
    nid,Title,Summary,Body
    1,Foo,Bar,Baz
    
  8. Go to /import. Use the default importer options, but upload the CSV file you created in the previous step, and click the Import button.
  9. When the import is complete, edit the node you created earlier. Note that the Title matches the value you put in the CSV (e.g.: mine was "Foo"). Note that the Summary matches the value you put in the CSV (e.g.: mine was "Bar"). Note that the body matches the value you put in the CSV (e.g.: mine was "Baz"). Note this likely matches your expectations.
  10. Edit the CSV file on your local machine. Remove the Body mapping, and change the Title and Summary to different values. For example, after editing, my CSV file was:
    nid,Title,Summary
    1,Fizz,Buzz
    
  11. Go to /admin/structure/feeds and edit the importer you created earlier. Go back to Processor -> Mappings. Remove the "Body" Mapping by checking the "Remove" checkbox in the last column of the table, and clicking the Save button at the bottom. Verify that the Body mapping no longer exists.
  12. Go to /import. Use the default importer options, but (re-)upload the CSV file you modified, and click the Import button.
  13. When the import is complete, edit the node you created earlier. Note that the Title matches the value you put in the CSV (e.g.: mine was "Fizz"). Note that the Summary matches the value you put in the CSV (e.g.: mine was "Buzz"). Note that the body is empty! Note that this likely does not match your expectations: I would expect it to be the value of the Body field before the import (i.e.: "Baz").

Analysis

The text_with_summary field has three (sub-)parts: a summary, a value and a format.

When text_feeds_set_target() builds a new value for the summary (sub-)part of the field, it modifies the entity loaded on line 280 of \FeedsProcessor::process() (that line calls \FeedsProcessor::entityLoad()).

In \FeedsProcessor::map(), there is code to reset the value of a field before data is mapped to it. However, because the definition for the summary field target has a 'real_target' property, \FeedsProcessor::map() resets the whole field, not just the summary.

Proposed resolution

Remove the 'real_target' property so that \FeedsProcessor::map() doesn't reset everything in the field.

Remaining tasks

  1. Write and upload a patch
  2. Review and feedback
  3. RTBC
  4. Commit

User interface changes

None.

API changes

None.

Data model changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mparker17 created an issue. See original summary.

mparker17’s picture

Issue summary: View changes

A bit of further investigation suggests that the data for that field should already have been loaded on line 280 of \FeedsProcessor::process() (which calls \FeedsProcessor::entityLoad()): the resulting entity has a $entity->body[LANGUAGE_NONE] array, which is empty (i.e.: missing the deltas for the field, as well as the sub-fields in each delta).

text_feeds_set_target() simply modifies that entity, so in theory, the existing code should work if the old data was being loaded to the correct place.

That means I should determine why that field does not load properly.

mparker17’s picture

Issue summary: View changes

Hmm... the entity loads fine; but inside \FeedsProcessor::map(), there is a chunk of code that resets it to an empty array:

    // 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->getMappings() as $mapping) {
      if (isset($targets[$mapping['target']]['real_target'])) {
        $target_name = $targets[$mapping['target']]['real_target'];
      }
      else {
        $target_name = $mapping['target'];
      }

      // If the target is a field empty the value for the targeted language
      // only.
      // In all other cases, just empty the target completely.
      if (isset($fields[$target_name])) {
        // Empty the target for the specified language.
        $target_item->{$target_name}[$mapping['language']] = array();
      }
      else {
        // Empty the whole target.
        $target_item->{$target_name} = NULL;
      }
    }
mparker17’s picture

Issue summary: View changes
Related issues: +#2309273: Mapping to text format

The target sub-fields are defined in text_feeds_processor_targets().

Not sure there will be a way to fix this bug without changing the configuration of all mappers, though: as far as I can tell, all text_with_summary fields will need separate :value and :summary fields in order to prevent the field mapper from eating the :value if the :summary is updated. :S

Now that I understand what's happening here, it is clear that this problem is closely related to #2309273: Mapping to text format — that allows mappings for the :format sub-field. If a patch in this issue or in #2309273: Mapping to text format is committed, it is likely that the other patch will need to be re-rolled, as they both touch roughly the same code.

mparker17’s picture

Issue summary: View changes
Status: Active » Needs review
FileSize
772 bytes

So, it turns out the solution is just to remove the 'real_target' definition for the summary field, and that's enough for Feeds to figure out that it shouldn't stomp the other values in the field. Much simpler than I initially thought.

mparker17’s picture

Added more-comprehensive documentation.

MegaChriz’s picture

From an user point of view I agree that this is an issue. From a technical point of view, it is officially a single field. The same problem exists for the link field: if you only map to "field_link:title" and not to "field_link:url", the title for the link will be updated, but the url will be erased (if there previously was a value).

Removing the "real_target" fixes the issue for ":summary", but it will also set $target_item->{'field_name:summary'} = NULL which doesn't necessary cause direct issues, but is not nice. I haven't tested it, but I think if the field is multivalued and you want to empty the summary of all values by providing an empty string, only the first one will be emptied.

Maybe we should define a new property for the target definition for this case? 'sub_field' maybe? Then, if defined, the map() method would only empty the values for that subfield.