Importing Paragraph Content with Feeds in Drupal 7
As I write this, there isn’t an established way to import data into Paragraphs using Feeds.
This solution only caters to my particular fields, but it should serve as an example of one way to import HTML content into a basic “textarea” paragraph.
Goal
My goal was to import HTML content into a Basic page content type as a basic textarea paragraph item. The original data is from a Drupal 7 typical “body” field, and I want to import the data into a paragraphs-based body field.
Environment Overview
My site has a Basic page content type with a paragraphs field (multi-value) having machine name field_s_paragraph and label of Body. This field allows for Basic Paragraph paragraphs. Basic Paragraph is a paragraph bundle with a single textarea field having machine name field_para_body. Here’s what this looks like:
Feeds Configuration
I’ve created a feed to import Basic page nodes. This feed uses the Node processor, configured to “Update existing nodes.”
Using the target defined in the code below, I’ve assigned the “body” field in my source data to the target Basic paragraph for field_s_paragraph. As I said earlier, this is specific to my particular needs. You should change as needed.
twentyfifteen_guid is just the node ID from the 2015 website. We’re importing into the new 2016 website.
How It Works
Feeds will temporarily store the value from the source field (body in my case) as paragraph_basic_temp using the newly-defined target. When we process new items we check for a value in that field. If there is one, we do some cleanup of the HTML, then create a new Basic Paragraph whose textarea contains the HTML. Lastly, we associate this new entity with the parent Basic page node.
When we re-process (update) nodes, we also check if the paragraph_basic_temp field exists. If it’s not empty, there are paragraphs in the node already, the first paragraph is a Basic Paragraph, and that paragraph is not the same as what we already have, we update the first paragraph entity to use the new HTML. This is specifically how I needed this to operate. You may need to adjust according to your needs.
Code
Here’s the code to make all of this happen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
/** * Implements hook_feeds_processor_targets_alter(). */ function mymodule_feeds_processor_targets_alter(&$targets, $type, $bundle) { if ($type == 'node') { $targets['paragraph_basic_temp'] = array( 'name' => t('Basic paragraph for field_s_paragraph'), 'description' => t('If populated and host entity has a field_s_paragraph field, this value will be saved as a basic paragraph to this field'), 'callback' => 'mymodule_feeds_paragraph_basic_set_target', ); } } /** * Mapping callback for the "Basic paragraph for field_s_paragraph" target */ function mymodule_feeds_paragraph_basic_set_target($source, $entity, $target, $value, $mapping) { if (!empty($value[0])) { $entity->paragraph_basic_temp = mymodule_clean_html($value[0]); } } /** * Implements hook_node_insert(). */ function mymodule_node_insert($node) { // If the node is being created and has a paragraph_basic_temp value from Feeds, // create and attach a new paragraph entity if (isset($node->paragraph_basic_temp)) { mymodule_create_attach_field_s_paragraph_basic_paragraph($node, $node->paragraph_basic_temp); } } /** * Implements hook_node_update(). */ function mymodule_node_update($node) { // If the node is being updated and has a paragraph_basic_temp value from Feeds, process it if (isset($node->paragraph_basic_temp)) { $node_wrapper = entity_metadata_wrapper('node', $node); // If there are paragraphs, and the first one is a paragraph_basic, then update it if (!empty($node_wrapper->field_s_paragraph[0])) { if ($node_wrapper->field_s_paragraph[0]->getBundle() == 'paragraph_basic') { $old_value = $node_wrapper->field_s_paragraph[0]->field_para_body->value(); if ($old_value['value'] != $node->paragraph_basic_temp) { $node_wrapper->field_s_paragraph[0]->field_para_body->set(array( 'value' => $node->paragraph_basic_temp, 'format' => 'full_html', )); $node_wrapper->field_s_paragraph[0]->save(); } } } else { // We have paragraph_basic_temp data we need to use, so create and attach a new paragraph entity mymodule_create_attach_field_s_paragraph_basic_paragraph($node, $node->paragraph_basic_temp); } } } /** * Creates a new basic paragraph entity and attaches it to field_s_paragraph * * @param object $node * Node to attach the paragraph entity to * @param string $content * Content for the entity */ function mymodule_create_attach_field_s_paragraph_basic_paragraph($node, $content) { // Create a new "Basic paragraph" paragraph item $paragraph = entity_create('paragraphs_item', array( 'field_name' => 'field_s_paragraph', 'bundle' => 'paragraph_basic') ); $paragraph->field_para_body[$node->language][0] = array( 'value' => $content, 'format' => 'full_html', ); $paragraph->setHostEntity('node', $node); $paragraph->save(TRUE); // Add the new paragraph item to the parent node $node->field_s_paragraph[$node->language][0] = array( 'value' => $paragraph->item_id, 'revision_id' => $paragraph->revision_id ); field_attach_update('node', $node); } /** * Cleans up HTML (probably not the most robust code, but it'll do) * * @param string $html * HTML to cleanup * * @return string * Cleaned HTML */ function mymodule_clean_html($html) { $html = str_replace("\n", ' ', $html); $html = str_replace('<b>', '<strong>', $html); $html = str_replace('</b>', '</strong>', $html); $replacement = ''; $patterns = array( '/<p>[ ]*<\/p>[ ]*/i', // These look like two spaces, but one is character 160 ); $html = preg_replace($patterns, $replacement, $html); return $html; } |
3 Comments
Phil B
Thank you for your example it has helped me a ton! I have my implementation working, but am curious why mymodule_create_attach_field_s_paragraph_basic_paragraph() does need to return $node.
It seems like the value of $node is being updated properly after the function is called but I don’t see anywhere it being passed by reference, so am confused how that part is working.
Thank you again.
Phil B
I think I figured it out, field_attach_update(‘node’, $node); saves the paragraph to an existing node, so returning it to hook_node_insert isn’t necessary.
Thanks again!
alex vier
hmmmmm,
does it make a difference if you would use ‘hook_feed_presave’ instead of ‘_node_update’ and ‘_node_insert’?
the outcome would be the same right?
just having all the stuff in one function instead..