Problem/Motivation

A site might have orphaned paragraphs that were assigned to now-deleted fields on the referenced parent entity. The code does not properly check if the paragraph's parent field still exists

Steps to reproduce

Proposed resolution

62:      $parentField = $entity->get('parent_field_name')->getString();
63:      $index = $this->getMultiValueIndex($parentEntity->$parentField->getValue(), $pid);

maybe we need a check like below?

$parentEntity->hasField($parentField)

Remaining tasks

User interface changes

API changes

Data model changes

Original report

I'm trying to replace content, and the configuration includes fields from paragraphs too

An AJAX HTTP error occurred.
HTTP Result Code: 500
Debugging information follows.
Path: /en/batch?id=34176&op=do_nojs&op=do
StatusText: error
ResponseText: The website encountered an unexpected error. Try again later.Error: Call to a member function getValue() on null in Drupal\scanner\Plugin\Scanner\Paragraph->handleParentRelationship() (line 63 of modules/contrib/scanner/src/Plugin/Scanner/Paragraph.php). Drupal\scanner\Plugin\Scanner\Entity->replace() (Line: 135)
Drupal\scanner\Form\ScannerConfirmForm::batchReplace() (Line: 297)
_batch_process() (Line: 139)
_batch_do() (Line: 95)
_batch_page() (Line: 52)
Drupal\system\Controller\BatchController->batchPage()
call_user_func_array() (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 637)
Drupal\Core\Render\Renderer->executeInRenderContext() (Line: 121)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext() (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 181)
Symfony\Component\HttpKernel\HttpKernel->handleRaw() (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle() (Line: 53)
Drupal\Core\StackMiddleware\Session->handle() (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle() (Line: 28)
Drupal\Core\StackMiddleware\ContentLength->handle() (Line: 50)
Drupal\ban\BanMiddleware->handle() (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle() (Line: 36)
Drupal\Core\StackMiddleware\AjaxPageState->handle() (Line: 124)
Drupal\cloudflare\CloudFlareMiddleware->handle() (Line: 51)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle() (Line: 741)
Drupal\Core\DrupalKernel->handle() (Line: 19)

Issue fork scanner-3587917

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

dimitriskr created an issue. See original summary.

damienmckenna’s picture

Do you see any errors when you do a search?

damienmckenna’s picture

We definitely need to get this fixed before 2.0.0 is released.

dimitriskr’s picture

Is the suggested solution ok? I didn't have this error on beta1, should I investigate my content structure when that happens or just go with it?

dimitriskr’s picture

Do you see any errors when you do a search?

No, there was nothing there

damienmckenna’s picture

Status: Active » Needs review

The code was heavily rewritten between beta1 and beta2, so it's likely a bug slipped in.

Looking at the MR:

     if ($parentEntityType == 'node') {
+      /** @var \Drupal\node\NodeInterface $parentEntity */
       $parentField = $entity->get('parent_field_name')->getString();
-      $index = $this->getMultiValueIndex($parentEntity->$parentField->getValue(), $pid);
+      if (!$parentEntity->hasField($parentField)) {
+        return;
+      }      $index = $this->getMultiValueIndex($parentEntity->$parentField->getValue(), $pid);

You need to add a line break before the $index= line.

Also, if $parentEntity doesn't have $parentField then how could it be a parent entity? Might that be a problem in the data? Are you able to edit that node and save it without any problems?

dimitriskr’s picture

My speculation is that the paragraph was created when that field existed in the parent node but since the field got deleted and the paragraph stayed there.
The code does not correctly identify orphaned paragraphs. I ran drush err:purge paragraph to clean them up and it seems it works now

Maybe using this service \Drupal::service('entity_reference_revisions.orphan_purger')->isUsed($entity) is better, near the edited lines?

dimitriskr’s picture

Title: Error on Replace Batch » Orphaned paragraphs are not thouroughly checked
Issue summary: View changes
dimitriskr’s picture

I also got this error

Error: Call to a member function getEntityTypeId() on null in Drupal\scanner\Plugin\Scanner\Paragraph->handleParentRelationship() (line 103 of /var/www/html/web/modules/contrib/scanner/src/Plugin/Scanner/Paragraph.php)

and \Drupal::service('entity_reference_revisions.orphan_purger')->isUsed($entity) reports both Entity and parent entity not being used

So locally I did this

diff --git a/web/modules/contrib/scanner/src/Plugin/Scanner/Paragraph.php b/web/modules/contrib/scanner/src/Plugin/Scanner/Paragraph.php
index 644ec2d64d..de72045463 100644
--- a/web/modules/contrib/scanner/src/Plugin/Scanner/Paragraph.php
+++ b/web/modules/contrib/scanner/src/Plugin/Scanner/Paragraph.php
@@ -100,7 +100,7 @@ protected function handleParentRelationship(mixed $entity, array $values, array
       $grandParentEntity = $parentEntity->getParentEntity();
       // Bail out if grandparent isn't a node, we only handle two levels of
       // nesting.
-      if ($grandParentEntity->getEntityTypeId() != 'node') {
+      if (!$grandParentEntity || $grandParentEntity->getEntityTypeId() != 'node') {
         return;
       }
       $parentField = $entity->get('parent_field_name')->getString();

damienmckenna changed the visibility of the branch 2.0.x to hidden.

damienmckenna’s picture

Version: 2.0.0-beta2 » 2.0.x-dev

With all the rewriting this week in how the replace operation works, I created a MR where it only processes nested entities where the root entity can be loaded, hopefully this will cover the problem. If needed we can expand it further, but let me know if this works for you.