There is an issue where new nested paragraphs are being deleted during workbench_moderation_store. The case is the following:

1- Create a bundle paragraph with some fields.
2- Create a second bundle with a paragraph field that accepts the first bundle with multiple values.
3- Create a content type and put a paragraph field that accepts the second bundle.
4- Create a node with only one item inside the field and publish it using workbench moderation.
5- Create a draft of that published node and add another item on the paragraph field.

When saving, during live version save, paragrahps_field_update is called for each paragraph item. When it's called for the items of the first bundle, parent entity is not a node, so it has not the "updating_live_revision" flag and then does all the update. In the end it gets deleted and even worse, the reference on the field is not deleted.

Posible solution
Looking at the code, I've found an apparently easy solution: Use the "updating_live_revision" flag on paragraphs_field_presave instead of paragraphs_field_update. My reasoning is that presave is being fired for all the items inside the parent paragraph item, so it gets to paragraphs_field_update and because parent paragraph doesn't have this flag, items not in live version are kept on $original_by_id variable and deleted without deleteing the parentEntity reference.

I don't know if paragraphs_field_presave is necessary on the "updating_live_revision" process, but I bet that those paragraphs fields shouldn't be updated for the live version of the node. So far, my testing has been positive. Incoming patch in next comment.

Comments

Pryrios created an issue. See original summary.

pryrios’s picture

recrit’s picture

Status: Active » Needs review
recrit’s picture

Status: Needs review » Reviewed & tested by the community

This patched worked for me. Live and current draft of the node now save correctly.
The paragraphs module is similar to the field collections module, and it implements the same code in field_collection_field_update().

  // Prevent workbench moderation from deleting field collections on 
  // node_save() during workbench_moderation_store(), when $host_entity->revision == 0.
  if (!empty($host_entity->workbench_moderation['updating_live_revision'])) {
    return;
  }
jstoller’s picture

I remember working on that Field Collection patch a couple years ago. For reference, that issue was #1807460: Field collection doesn't play nice with workbench moderation (patch).

farse’s picture

This patch has come not a moment too soon, worked for me too!

rp7’s picture

Patch in #2 works. Thanks!

jstoller’s picture

Posting a related issue that also effects Workbench Moderation users and could use some help: #2609754: Deleting forward revision confuses Paragraphs. In the mean time, be careful when deleting draft revisions!

NOTE: This other issue should have no impact on the patch available here, which is already RTBC. It just has a similar audience.

markconroy’s picture

Hi Guys,

I have been working on a rather large (multilingual) website and this issue appeared. Actually took a while to track down that the issue was paragraphs nested inside paragraphs in draft state in workbench moderation-enabled nodes.

I applied the patch from #2 above, and all seems to be working fine so far. Thanks a lot.

Yaron Tal’s picture

Status: Reviewed & tested by the community » Active
Related issues: +#2243595: Nested field collections don't play nice with workbench moderation (revisions)
StatusFileSize
new973 bytes

As also mentioned in #2243595: Nested field collections don't play nice with workbench moderation (revisions), both paragraphs and field_collections have some trouble with workbench_moderation when nested. Attached patch will keep checking for host entities until no higher level is possible before checking for the workbench_moderation flag.

Yaron Tal’s picture

Status: Active » Needs review
jbaum_13’s picture

Patch #2 works to resolve issue with paragraph content added by editors being deleted when a draft is saved. I am however experiencing an issue with the published paragraph bundle content being hidden to unauthenticated users when there is a draft version of the page saved.

gadaniels72’s picture

Status: Needs review » Reviewed & tested by the community

Tested patch #11 and it does solve the problem with paragraphs being lost when workbench moderation is used. Followed the steps in the issue summary and verified:

  • The added second item exists in the Draft revision
  • The published revision was correct (did not have the 2nd item)
  • Publishing the draft revision, the published revision was correct (had the 2nd item); verified that all prior revisions remained unaltered.

Ran two additional scenarios to test where the paragraphs were more deeply nested and had the same successful results. One added another unlimited paragraph bundle to the nested item (so I had content type->paragraph bundle->paragraph bundle->paragraph bundle) and one that added another unlimited paragraph bundle to the origin item (so content type->paragraph bundle->2 different nested bundles)

jeroen.b’s picture

Status: Reviewed & tested by the community » Needs work

Thanks @Yaron Tal, looking good.

I'm currently trying to get the quality of the code up to par with the D8 version.
That mostly just means we need to add many tests to make sure we won't break anything.
Are you able to create a unit test for this? (and provide 2 patches: 1 with the fixed code + test, 1 with the test only. that way we can see the test actually confirms it is fixed now).

eelkeblok’s picture

I am running into a seemingly highly related issue with an image field, where a newly uploaded image (through media) is removed immediately when saving the node. Researching this I noticed that patch 11 moves the logic from hook_field_presave (as it was in patch 2) to hook_field_update. I discussed with Yaron (author of #11) and he couldn't remember the reason. Possibly because that was simply what was done in the field_collection module as well. My issue with the image is resolved when I move the logic from #11 back to _presave. I just have to do some testing to see if the original issue is solved as well (maybe write those tests...).

eelkeblok’s picture

Assigned: Unassigned » eelkeblok
eelkeblok’s picture

Assigned: eelkeblok » Unassigned

I'm hitting a brick wall trying to get a test case going for this. Here's what I have (if you want to try, copy this into paragraphs.test).

/**
 * Defines tests for paragraphs combined with Panelizer.
 */
class ParagraphsWorkbenchModerationTestCase extends DrupalWebTestCase {
  protected $privilegedUser;

  /**
   * Give display information to the SimpleTest system.
   *
   * getInfo() returns a keyed array of information for SimpleTest to show.
   *
   * It's a good idea to organize your tests consistently using the 'group'
   * key.
   */
  public static function getInfo() {
    return array(
      'name' => 'Paragraphs Workbench Moderation tests',
      'description' => 'Ensure that Paragraphs works correctly when combined with Workbench Moderation.',
      'group' => 'Paragraphs',
    );
  }

  /**
   * {@inheritdoc}
   */
  public function setUp() {
    $modules = array(
      'paragraphs',
      'workbench_moderation',
    );
    parent::setUp($modules);

    $this->privilegedUser = $this->drupalCreateUser(array(
      'create article content',
      'view all unpublished content',
      'edit any article content',
      'moderate content from draft to needs_review',
      'moderate content from needs_review to published',
      'view moderation history',
    ));
    $this->drupalLogin($this->privilegedUser);

    $bundle = new stdClass();

    // Set up a content structure.
    // Start with the two paragraph levels.
    $bundle = new stdClass();
    $bundle->bundle = 'inner_paragraph';
    $bundle->name = 'Inner paragraph';
    $bundle->locked = 1;
    paragraphs_bundle_save($bundle);

    // Wrapper paragraph.
    $bundle = new stdClass();
    $bundle->bundle = 'outer_paragraph';
    $bundle->name = 'Outer paragraph';
    $bundle->locked = 1;
    paragraphs_bundle_save($bundle);

    // Create a content field to be able to detect we have an instance of the
    // nested paragraph.
    $content_field = field_create_field(array(
      'field_name' => 'field_content',
      'type' => 'text',
    ));

    field_create_instance(array(
      'field_id' => $content_field['id'],
      'field_name' => $content_field['field_name'],
      'label' => 'Content',
      'entity_type' => 'paragraphs_item',
      'bundle' => 'inner_paragraph',
    ));

    // Create the field on the outer paragraph to contain the inner paragraph.
    $inner_paragraph_field = field_create_field(array(
      'field_name' => 'field_paragraphs_nested',
      'type' => 'paragraphs',
      'cardinality' => -1,
    ));

    field_create_instance(array(
      'field_id' => $inner_paragraph_field['id'],
      'field_name' => $inner_paragraph_field['field_name'],
      'entity_type' => 'paragraphs_item',
      'bundle' => 'outer_paragraph',
      'label' => 'Nested paragraphs',
      'widget' => array (
        'weight' => '1',
        'type' => 'paragraphs_embed',
        'module' => 'paragraphs',
        'active' => 0,
        'settings' => array (),
      ),
      'settings' =>
        array (
          'title' => 'Paragraph',
          'title_multiple' => 'Paragraphs',
          'default_edit_mode' => 'open',
          'default_edit_mode_override' => '1',
          'add_mode' => 'select',
          'allowed_bundles' =>
            array (
              'inner_paragraph' => 'inner_paragraph',
              'outer_paragraph' => -1,
            ),
          'bundle_weights' =>
            array (
              'inner_paragraph' => '2',
              'outer_paragraph' => '3',
            ),
          'user_register_form' => false,
        ),

    ));

    // Create the field on the article node type to contain the outer paragraph.
    // Create the field on the outer paragraph to contain the inner paragraph.
    $outer_paragraph_field = field_create_field(array(
      'field_name' => 'field_paragraphs',
      'type' => 'paragraphs',
      'cardinality' => -1,
    ));

    field_create_instance(array(
      'field_id' => $outer_paragraph_field['id'],
      'field_name' => $outer_paragraph_field['field_name'],
      'entity_type' => 'node',
      'bundle' => 'article',
      'label' => 'Paragraphs',
      'widget' => array (
        'weight' => '1',
        'type' => 'paragraphs_embed',
        'module' => 'paragraphs',
        'active' => 0,
        'settings' => array (),
      ),
      'settings' =>
        array (
          'title' => 'Paragraph',
          'title_multiple' => 'Paragraphs',
          'default_edit_mode' => 'open',
          'default_edit_mode_override' => '1',
          'add_mode' => 'select',
          'allowed_bundles' =>
            array (
              'inner_paragraph' => -1,
              'outer_paragraph' => 'outer_paragraph',
            ),
          'bundle_weights' =>
            array (
              'inner_paragraph' => '2',
              'outer_paragraph' => '3',
            ),
          'user_register_form' => false,
        ),
    ));

    // Enable workbench moderation for content type Article.
    variable_set('node_options_article', array(
      0 => 'promote',
      1 => 'moderation',
      2 => 'revision',
    ));

    variable_set('workbench_moderation_default_state_article', 'draft');
  }

  /**
   * Tests whether nested paragraphs are retained when saving a node as a draft.
   */
  function testNestedParagraphsWithWorkbenchModeration() {
    $content = $this->randomName();

    $this->drupalGet('node/add/article');

    // Add the outer paragraph
    $this->drupalPostAJAX(NULL, array(), 'field_paragraphs_add_more_add_more');

    // Add the inner paragraph
    $this->drupalPostAJAX(NULL, array(), 'field_paragraphs_und_0_field_paragraphs_nested_add_more_add_more');

    // Enter some content and submit the form.
    $this->drupalPost(
      NULL,
      array(
        'title' => $this->randomName(),
        'field_paragraphs[und][0][field_paragraphs_nested][und][0][field_content][und][0][value]' => $content,
      ),
      'Save'
    );

    // Moderate the node to 'Needs review'.
    $this->clickLink('Moderate');
    $this->drupalPost(NULL, array(), 'Apply');

    // Moderate the node to 'Published'.
    $this->drupalPost(NULL, array(), 'Apply');

    // Go to the published node.
    $this->clickLink('View published');

    // See if the content is available in the output.
    $this->assertText($content, "Found the content from the nested paragraph in the output");

    // Create a new draft.
    $this->clickLink('New draft');

    // Add another nested paragraph.
    $this->drupalPostAJAX(NULL, array(), 'field_paragraphs_und_0_field_paragraphs_nested_add_more_add_more');

    $new_content = $this->randomName();
    $this->drupalPost(
      NULL,
      array(
        'field_paragraphs[und][0][field_paragraphs_nested][und][1][field_content][und][0][value]' => $new_content,
      ),
      'Save'
    );

    // See if the content is available in the output.
    $this->assertText($new_content, "Found the extra content from the second nested paragraph in the draft");
  }
}

For some reason, the last step seems to completely remove all nested paragraphs, while the expected behaviour (without the patch) is that only the newly added content is not found (adding the patch doesn't change anything either). When reproducing this manually, these steps reproduce the issue (i.e. the originally added content is preserved in the draft). Can anyone spot a problem with the above test?

ben.kyriakou’s picture

I've encountered this issue today in a separate context to where I've found it before, and in an even more broken way. When using Paragraphs in conjunction with Entity Reference and Workbench Moderation, the following will result in AJAX errors:

- Create a node type NodeType1 and NodeType2
- Create a paragraph type ParagraphType1 with an entity reference field to NodeType2. Add this to NodeType1
- Create a node of type NodeType2 and publish it
- Create a node of type NodeType1 with a paragraph field value and publish it
- Create a draft of this node and add another paragraph field
- Edit this node and try to add a reference in the second paragraph field

You'll get a 403 error on the path entityreference/autocomplete/single/field_para_files_ref/paragraphs_item/para_file/[id] which, if you spend a lot of time digging into it, this turns out to be an access issue in paragraphs_paragraphs_item_access() because it believes the paragraph doesn't have a hostEntity. Applying the above patch somehow makes it worse, since it then believes all paragraphs loaded in this way don't have a hostEntity.

Eventually I tracked this down to the loading of the host entity in ParagraphsItemEntity->fetchHostDetails(). The checks that it does for ParagraphsItemEntity::isInUse() always fall over when it thinks the item isn't using the default entity, which most cases with workbench moderation is flawed. I'm not sure of the intent of this, and the interactions with the archived flag, but changing this code to simply load in the host entity by revision every time did the trick for me. This still seems to preserve the draft attachment behaviour, with the added bonus that you can load in draft paragraphs and retrieve the correct host entity.

Patch attached - still requires tests and may need some tidying, but it passes the existing tests so hopefully isn't breaking anything further.

ben.kyriakou’s picture

Status: Needs work » Needs review

Moving to Needs Review for testbot.

chrisolof’s picture

After running into the image field issues outlined comment #16 here and in issues #2665662: Paragraph and Workbench Moderation Image Issue and #2658244: Para image field edit not saved on node save as draft (moderation), I can confirm that both the patch in comment #2 and the patch in comment #19 solved it for me. Either one works. The patch in #11 does not fix the image issue - likely for the reasons outlined by @eelkeblok in comment #16.

josephdpurcell’s picture

Priority: Major » Critical

Setting to critical given there is data loss.

jazzslider’s picture

I've spent some time this afternoon trying to reproduce the issue on Drupal 8 —it seems to occur there too, when I follow the OP's steps.

With that in mind, I'm wondering —is there a D8-portable fix for this as well as the D7 patch already in the queue? At the moment I'm not terribly familiar with the extent to which Paragraphs was rearchitected for D8.

capfive’s picture

Status: Needs review » Reviewed & tested by the community

I ran into this issue with a file field.

Have a node which has an embedded paragraphs field which allows for unlimited values.

First paragraph saves, second paragraph wont save unless negating workbench and going straight to published.

After applying patch #19 it now works as intended.

Based on chrisolof's test as well as my own I think we can mark this as R&T :)

Thank you for the hard work, this saved my backside for a client that was relying on both these working in unison together.

Yaron Tal’s picture

Status: Reviewed & tested by the community » Needs work

Still needs tests

capfive’s picture

Status: Needs work » Needs review

Ok then we should put it to needs review then?

eelkeblok’s picture

Status: Needs review » Needs work

It needs automated tests in the patch :) So the patch needs work. See #18 for a possible starting point for a test case.

Sridar’s picture

#2 worked for me. It's been one week that we are struggling to fix this. Thanks @Pryrios

josephdpurcell’s picture

Issue tags: +Needs tests, +Dublin2016

Adding "Needs tests" since that's all that's left. And adding "Dublin2016" in case any sprinters would like to work on this.

oualid-ezr’s picture

Status: Needs work » Needs review

#2 worked for me too!

jstoller’s picture

Status: Needs review » Needs work
Issue tags: -Dublin2016

Still needs unit tests, re: #15.

solideogloria’s picture

Status: Needs work » Reviewed & tested by the community

Obviously, no one cares to create unit tests for D7. Just include the fix. If unit tests are necessary, create a separate issue for them.

solideogloria’s picture

Someone may need to reroll the patch

jstoller’s picture

Here's a re-roll of the patch from #19 against the latest dev. There are no additional code changes.

jstoller’s picture

Adding back the fix from #11 that was dropped and re-rolling against dev.

  • jstoller committed 4429ea5 on 7.x-1.x
    Issue #2603424 by jstoller, Pryrios, Yaron Tal, ben.kyriakou, eelkeblok...
jstoller’s picture

Status: Reviewed & tested by the community » Fixed

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.