This feels critical to me as it took a live site down, but change priority if needed.

Problem/Motivation

When deleting Event Series content, any associated Registrants are not deleted. When visiting parts of the website needing to get Registrant data, the missing Event Series cause their Registrants to produce an error:

Error: Call to a member function id() on null in Drupal\recurring_events_registration\Entity\Registrant->urlRouteParameters() (line 450 of /web/modules/contrib/recurring_events/modules/recurring_events_registration/src/Entity/Registrant.php)

Full error below

Steps to reproduce

- Create an Event Series with at least one Instance and with Registration enabled.

- Add Registrants to the Event Instance.

- Delete the Event Series

- Go to /admin/content/events/registrations and get WSOD

- Check log and see the error message

Manually deleting all registrants and associated field data from the database restores the page and stops errors from occurring.

Proposed resolution

When deleting Event Series, all Registrants of all Event Instances for the series should also be deleted.

Comments

endless_wander created an issue. See original summary.

chrisla’s picture

Full error message:

Error: Call to a member function id() on null in Drupal\recurring_events_registration\Entity\Registrant->urlRouteParameters() (line 450 of /home/website/domains/website.ca/web/modules/contrib/recurring_events/modules/recurring_events_registration/src/Entity/Registrant.php)
#0 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Entity/EntityBase.php(174): Drupal\recurring_events_registration\Entity\Registrant->urlRouteParameters('edit-form')
#1 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Entity/EntityListBuilder.php(136): Drupal\Core\Entity\EntityBase->toUrl('edit-form')
#2 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Entity/EntityListBuilder.php(112): Drupal\Core\Entity\EntityListBuilder->getDefaultOperations(Object(Drupal\recurring_events_registration\Entity\Registrant))
#3 /home/website/domains/website.ca/web/core/modules/views/src/Plugin/views/field/EntityOperations.php(131): Drupal\Core\Entity\EntityListBuilder->getOperations(Object(Drupal\recurring_events_registration\Entity\Registrant))
#4 /home/website/domains/website.ca/web/core/modules/views/src/Plugin/views/field/FieldPluginBase.php(1156): Drupal\views\Plugin\views\field\EntityOperations->render(Object(Drupal\views\ResultRow))
#5 /home/website/domains/website.ca/web/core/modules/views/views.theme.inc(238): Drupal\views\Plugin\views\field\FieldPluginBase->advancedRender(Object(Drupal\views\ResultRow))
#6 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Theme/ThemeManager.php(287): template_preprocess_views_view_field(Array, 'views_view_fiel...', Array)
#7 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/Renderer.php(422): Drupal\Core\Theme\ThemeManager->render('views_view_fiel...', Array)
#8 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/Renderer.php(201): Drupal\Core\Render\Renderer->doRender(Array, false)
#9 /home/website/domains/website.ca/web/core/modules/views/src/Plugin/views/field/FieldPluginBase.php(1745): Drupal\Core\Render\Renderer->render(Array)
#10 /home/website/domains/website.ca/web/core/modules/views/src/Plugin/views/style/StylePluginBase.php(779): Drupal\views\Plugin\views\field\FieldPluginBase->theme(Object(Drupal\views\ResultRow))
#11 [internal function]: Drupal\views\Plugin\views\style\StylePluginBase->elementPreRenderRow(Array)
#12 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php(101): call_user_func_array(Array, Array)
#13 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/Renderer.php(772): Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_ren...', 'exception', 'Drupal\\Core\\Ren...')
#14 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/Renderer.php(363): Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array)
#15 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/Renderer.php(201): Drupal\Core\Render\Renderer->doRender(Array, false)
#16 /home/website/domains/website.ca/web/core/modules/views/src/Plugin/views/style/StylePluginBase.php(718): Drupal\Core\Render\Renderer->render(Array)
#17 /home/website/domains/website.ca/web/core/modules/views/src/Plugin/views/style/StylePluginBase.php(584): Drupal\views\Plugin\views\style\StylePluginBase->renderFields(Array)
#18 /home/website/domains/website.ca/web/core/modules/views/src/Plugin/views/style/StylePluginBase.php(475): Drupal\views\Plugin\views\style\StylePluginBase->renderGrouping(Array, Array, true)
#19 /home/website/domains/website.ca/web/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php(2170): Drupal\views\Plugin\views\style\StylePluginBase->render(Array)
#20 /home/website/domains/website.ca/web/core/modules/views/src/ViewExecutable.php(1533): Drupal\views\Plugin\views\display\DisplayPluginBase->render()
#21 /home/website/domains/website.ca/web/core/modules/views/src/Plugin/views/display/Page.php(199): Drupal\views\ViewExecutable->render()
#22 /home/website/domains/website.ca/web/core/modules/views/src/ViewExecutable.php(1630): Drupal\views\Plugin\views\display\Page->execute()
#23 /home/website/domains/website.ca/web/core/modules/views/src/Element/View.php(81): Drupal\views\ViewExecutable->executeDisplay('admin_page', Array)
#24 [internal function]: Drupal\views\Element\View::preRenderViewElement(Array)
#25 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Security/DoTrustedCallbackTrait.php(101): call_user_func_array(Array, Array)
#26 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/Renderer.php(772): Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_ren...', 'exception', 'Drupal\\Core\\Ren...')
#27 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/Renderer.php(363): Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array)
#28 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/Renderer.php(201): Drupal\Core\Render\Renderer->doRender(Array, false)
#29 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(241): Drupal\Core\Render\Renderer->render(Array, false)
#30 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/Renderer.php(564): Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}()
#31 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(242): Drupal\Core\Render\Renderer->executeInRenderContext(Object(Drupal\Core\Render\RenderContext), Object(Closure))
#32 /home/website/domains/website.ca/web/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php(132): Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object(Symfony\Component\HttpFoundation\Request), Object(Drupal\Core\Routing\CurrentRouteMatch))
#33 /home/website/domains/website.ca/web/core/lib/Drupal/Core/EventSubscriber/MainContentViewSubscriber.php(90): Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object(Symfony\Component\HttpFoundation\Request), Object(Drupal\Core\Routing\CurrentRouteMatch))
#34 [internal function]: Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object(Symfony\Component\HttpKernel\Event\ViewEvent), 'kernel.view', Object(Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher))
#35 /home/website/domains/website.ca/web/core/lib/Drupal/Component/EventDispatcher/ContainerAwareEventDispatcher.php(142): call_user_func(Array, Object(Symfony\Component\HttpKernel\Event\ViewEvent), 'kernel.view', Object(Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher))
#36 /home/website/domains/website.ca/vendor/symfony/http-kernel/HttpKernel.php(163): Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch(Object(Symfony\Component\HttpKernel\Event\ViewEvent), 'kernel.view')
#37 /home/website/domains/website.ca/vendor/symfony/http-kernel/HttpKernel.php(80): Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object(Symfony\Component\HttpFoundation\Request), 1)
#38 /home/website/domains/website.ca/web/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\Component\HttpKernel\HttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#39 /home/website/domains/website.ca/web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\Core\StackMiddleware\Session->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#40 /home/website/domains/website.ca/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(106): Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#41 /home/website/domains/website.ca/web/core/modules/page_cache/src/StackMiddleware/PageCache.php(85): Drupal\page_cache\StackMiddleware\PageCache->pass(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#42 /home/website/domains/website.ca/web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\page_cache\StackMiddleware\PageCache->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#43 /home/website/domains/website.ca/web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#44 /home/website/domains/website.ca/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#45 /home/website/domains/website.ca/web/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\StackedHttpKernel->handle(Object(Symfony\Component\HttpFoundation\Request), 1, true)
#46 /home/website/domains/website.ca/web/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#47 {main}
chrisla’s picture

Issue summary: View changes
owenbush’s picture

Thank you for raising this issue. I'll take a look at it as soon as I can.

owenbush’s picture

I have so far been unable to recreate this problem.

There is a hook 'recurring_events_registration_recurring_events_pre_delete_instances' which fires when a series entity is removed, which will delete the registrants prior to deleting the instances which itself happens prior to deleting the underlying series.

This seemed to fire for me when deleted through the Admin UI by clicking on Delete.

Did you do anything special when deleting? Like using views bulk operations, or doing it programmatically?

Hopefully we can figure out what is going on so I can address the problem.

chrisla’s picture

cannot replicate on a fresh install. was just deleting Event Series via its delete button. Will continue to investigate what is needed to reproduce

owenbush’s picture

Thank you, I really appreciate that. I'll also keep an eye out for it.

christian deloach’s picture

I can confirm the same problem. Taking a look at the database I have three registrations. One of the registrations is linked (eventinstance_id) to an eventinstance that no longer exists. If I update the Registration view's relationships, changing the "Event Instance entity" to be required, the view displays without the above mentioned error, displaying the two registrations linked to eventinstance records that still exist in the database. It's possible this may have been caused by editing an event series with a recur type set to "Custom/Single Event". If the event date is changed, a warning is displayed that all instances will be removed and recreated. I suspect, but haven't confirmed through the code, the eventinstance record was removed but the registration was not deleted and the registration was not assigned the new eventinstance_id. I'm currently running on 2.0.0-rc3 but will update to 2.0.0-rc4 to see if recent updates (e.g. https://www.drupal.org/project/recurring_events/issues/3282741) may have address the issue.

vlad.dancer’s picture

Hm.. We run in the same issue but on updating Event Instance with single/custom events.
We are using also Autosave Form module that may conflict with Recurring Events.
From what I see Event Series had updated the date field but event instance is not.
Continue research on this.

owenbush’s picture

I just tried setting up an event series using the Custom/Single Event recurrence type, I then registered twice for an instance of that series. Then I updated the event series and changed the date, I was prompted as expected if I was sure I wanted to do this and that existing instances would be deleted, I continued and the event instance was removed as well as the two registrations.

So, I am still unable to reliably recreate this issue and I'm not sure why.

christian deloach’s picture

Thank you Owen. I've not been able to replicate this on a clean installation using 2.0.0-rc4 so it's possible the issue may have been fixed between rc3 and rc4, maybe the committed patch in https://www.drupal.org/project/recurring_events/issues/3282741.

owenbush’s picture

Yea maybe that was the underlying issue, lets keep this open for a while longer and if there are no further reports I'll close the issue out. Thanks for all involved for helping debug and troubleshoot.

vlad.dancer’s picture

Status: Active » Needs review

I'm not sure of the our issue related to the original one, if not feel free move it into a new issue.
But here is backtrace:

The website encountered an unexpected error. Please try again later.
Error: Call to a member function id() on null in Drupal\recurring_events_registration\Entity\Registrant->urlRouteParameters() (line 380 of modules/contrib/recurring_events/modules/recurring_events_registration/src/Entity/Registrant.php).

Drupal\recurring_events_registration\Entity\Registrant->urlRouteParameters('canonical') (Line: 174)
Drupal\Core\Entity\EntityBase->toUrl('canonical') (Line: 291)
Drupal\Core\Entity\EntityBase->Drupal\Core\Entity\{closure}('canonical')
array_filter(Array, Object) (Line: 300)
Drupal\Core\Entity\EntityBase->uriRelationships() (Line: 105)
menu_link_content_entity_predelete(Object)
call_user_func_array(Object, Array) (Line: 426)
Drupal\Core\Extension\ModuleHandler->Drupal\Core\Extension\{closure}(Object, 'menu_link_content') (Line: 405)
Drupal\Core\Extension\ModuleHandler->invokeAllWith('entity_predelete', Object) (Line: 433)
Drupal\Core\Extension\ModuleHandler->invokeAll('entity_predelete', Array) (Line: 251)
Drupal\Core\Entity\EntityStorageBase->invokeHook('predelete', Object) (Line: 903)
Drupal\Core\Entity\ContentEntityStorageBase->invokeHook('predelete', Object) (Line: 486)
Drupal\Core\Entity\EntityStorageBase->delete(Array) (Line: 751)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->delete(Array) (Line: 347)
Drupal\Core\Entity\EntityBase->delete() (Line: 219)
recurring_events_registration_recurring_events_save_pre_instances_deletion(Object)
call_user_func_array(Object, Array) (Line: 426)
Drupal\Core\Extension\ModuleHandler->Drupal\Core\Extension\{closure}(Object, 'recurring_events_registration') (Line: 405)
Drupal\Core\Extension\ModuleHandler->invokeAllWith('recurring_events_save_pre_instances_deletion', Object) (Line: 433)
Drupal\Core\Extension\ModuleHandler->invokeAll('recurring_events_save_pre_instances_deletion', Array) (Line: 404)
Drupal\recurring_events\EventCreationService->clearEventInstances(Object) (Line: 222)
recurring_events_eventseries_update(Object)
call_user_func_array(Object, Array) (Line: 426)
Drupal\Core\Extension\ModuleHandler->Drupal\Core\Extension\{closure}(Object, 'recurring_events') (Line: 405)
Drupal\Core\Extension\ModuleHandler->invokeAllWith('eventseries_update', Object) (Line: 433)
Drupal\Core\Extension\ModuleHandler->invokeAll('eventseries_update', Array) (Line: 249)
Drupal\Core\Entity\EntityStorageBase->invokeHook('update', Object) (Line: 903)
Drupal\Core\Entity\ContentEntityStorageBase->invokeHook('update', Object) (Line: 598)
Drupal\Core\Entity\EntityStorageBase->doPostSave(Object, 1) (Line: 784)
Drupal\Core\Entity\ContentEntityStorageBase->doPostSave(Object, 1) (Line: 523)
Drupal\Core\Entity\EntityStorageBase->save(Object) (Line: 802)
Drupal\Core\Entity\Sql\SqlContentEntityStorage->save(Object) (Line: 339)
Drupal\Core\Entity\EntityBase->save() (Line: 285)
Drupal\Core\Entity\EntityForm->save(Array, Object) (Line: 392)
Drupal\recurring_events\Form\EventSeriesForm->save(Array, Object)
call_user_func_array(Array, Array) (Line: 114)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 52)
Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 592)
Drupal\Core\Form\FormBuilder->processForm('eventseries_default_edit_form', Array, Object) (Line: 144)
Drupal\autosave_form\Form\AutosaveFormBuilder->processForm('eventseries_default_edit_form', Array, Object) (Line: 320)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 97)
Drupal\autosave_form\Form\AutosaveFormBuilder->buildForm(Object, Object) (Line: 73)
Drupal\Core\Controller\FormController->getContentResult(Object, Object) (Line: 39)
Drupal\layout_builder\Controller\LayoutBuilderHtmlEntityFormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 564)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 159)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 81)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 709)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

It seems that menu_link_content_entity_predelete tries to get referenced entities (registrations) too late?
So I've tried to fix Registrant::urlRouteParameters()

  protected function urlRouteParameters($rel) {
    $uri_route_parameters = parent::urlRouteParameters($rel);
    $event_instance = $this->getEventInstance();

    $event_instance_id = $this->getEventInstance()
      ? $event_instance->id()
      : NULL;

    $uri_route_parameters['eventinstance'] = $event_instance_id;
    $uri_route_parameters['registrant'] = $this->id();
    if ($rel == 'anon-edit-form' || $rel == 'anon-delete-form') {
      $uri_route_parameters['uuid'] = $this->uuid->value;
    }
    return $uri_route_parameters;
  }

But then I have troubles with redirect_entity_delete() trying to lookup and delete redirect for registrant entity and $entity->toUrl('canonical')->isRouted() causing fatal error. I see that redirect_entity_delete catches RouteNotFoundException here:

function redirect_entity_delete(EntityInterface $entity) {
  try {
    if ($entity->getEntityType()->hasLinkTemplate('canonical') && $entity->toUrl('canonical')->isRouted()) {
      redirect_delete_by_path('internal:/' . $entity->toUrl('canonical')->getInternalPath());
      redirect_delete_by_path('entity:' . $entity->getEntityTypeId() . '/' . $entity->id());
    }
  }
  catch (RouteNotFoundException $e) {
    // This can happen if a module incorrectly defines a link template, ignore
    // such errors.
  }
}

But it seems that neither hasLinkTemplate() nor toUrl() throws RouteNotFoundException. toUrl (by interface) should throw either UndefinedLinkTemplateException or EntityMalformedException exception.
So I assume that redirect has wrong catch statement and should be updated.
Just for the test I've changed Registrant::toUrl():

  public function toUrl($rel = 'canonical', array $options = []) {
    return $this->getEventInstance() === NULL
      ? throw new RouteNotFoundException()
      : parent::toUrl($rel, $options);
  }

And now all fatal errors were gone. I'm going to report an issue in redirect module too.

So the final fix should be:
Registrant.php

  public function uriRelationships() {
    return $this->getEventInstance() === NULL
      ? []
      : parent::uriRelationships();
  }

  public function toUrl($rel = 'canonical', array $options = []) {
    return $this->getEventInstance() === NULL
      ? throw new UndefinedLinkTemplateException()
      : parent::toUrl($rel, $options);
  }

redirect.module

function redirect_entity_delete(EntityInterface $entity) {
  try {
    if ($entity->getEntityType()->hasLinkTemplate('canonical') && $entity->toUrl('canonical')->isRouted()) {
      redirect_delete_by_path('internal:/' . $entity->toUrl('canonical')->getInternalPath());
      redirect_delete_by_path('entity:' . $entity->getEntityTypeId() . '/' . $entity->id());
    }
  }
  catch (UndefinedLinkTemplateException | RouteNotFoundException $e) {
    // This can happen if a module incorrectly defines a link template, ignore
    // such errors.
  }
}

Need to test more cases for the fix above.

mikaylaflanagan’s picture

Can confirm this issue is still happening - just ran into the same issue today. The steps to reproduce listed above pretty much exactly match what caused this issue for me.

christian deloach’s picture

I too can confirm that this issue is still happening using rc5.

chrisla’s picture

Issue summary: View changes
vlad.dancer’s picture

We are using one patch for recurring_events and one for redirect.

vlad.dancer’s picture

And this one for redirect

owenbush’s picture

I think I may have found the source for the orphaned registrants that cause this issue.

I have created an MR in #3315836: Deleting old events with registrants that hopefully addresses the problem, and also provides a UI for cleaning up orphaned registrants and instances if some have ended up not getting deleted.

I haven't really had a chance to really review vlads patches and I will try and get to those later, but in the meantime we may be able to get out of the issue in a self-service manner.

Info is in the other issue.