Problem/Motivation

Webform questions currently inherit \Drupal\webform\WebformElementInterface::postDelete but this is only called when a submission is deleted, not when a WebformElement is deleted. For complex custom question types (ones which contain uploaded files in my case) it would be helpful to have a callback for when the WebformElement itself is deleted so that uploaded data can be removed as well.

Proposed resolution

Create a callback that gets triggered when a WebformElement is deleted.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

bobby.gryzynger created an issue. See original summary.

bobbygryzynger’s picture

Issue summary: View changes
jrockowitz’s picture

I think we may need to add the below methods to the WebformHandlerInterface

  public function createElement(array $element) {}
  public function updateElement((array $element) {}
  public function deleteElement((array $element) {}

One limitation is bulk updating elements probably can't trigger these callbacks.

bobbygryzynger’s picture

@jrockowitz that set of methods would take care of my use case, although what's the particular reason they wouldn't be called during bulk operations?

jrockowitz’s picture

When editing the element's YAML source, I am not sure I am up for writing the code for figuring which elements were created, updated, or deleted.

  • jrockowitz committed 670dfc6 on 2879122-element-crud-hooks
    Issue #2879122: Create callback for question removal
    
jrockowitz’s picture

Status: Active » Needs review
FileSize
8.14 KB

I figured out how to track all element CRUD operations, even when editing the YAML source.

The attached patch still needs some basic test coverage.

Status: Needs review » Needs work

The last submitted patch, 7: create_callback_for-2879122-6.patch, failed testing.

  • jrockowitz committed 82a150b on 2879122-element-crud-hooks
    Issue #2879122: Create callback for question removal. Add tests
    
jrockowitz’s picture

Status: Needs work » Needs review
FileSize
12.18 KB

Status: Needs review » Needs work

The last submitted patch, 10: create_callback_for-2879122-9.patch, failed testing.

  • jrockowitz committed 5e25961 on 2879122-element-crud-hooks
    Issue #2879122: Create callback for question removal. Fix tests
    
jrockowitz’s picture

Status: Needs work » Needs review
FileSize
12.59 KB

Status: Needs review » Needs work

The last submitted patch, 13: create_callback_for-2879122-12.patch, failed testing.

  • jrockowitz committed da53959 on 2879122-element-crud-hooks
    Issue #2879122: Create callback for question removal. Fix tests
    
jrockowitz’s picture

Status: Needs work » Needs review
FileSize
12.66 KB

  • jrockowitz committed 7f85e82 on 8.x-5.x
    Issue #2879122 by jrockowitz: Create callback for question removal
    
jrockowitz’s picture

Status: Needs review » Fixed

I applied the patch. Please download the latest dev release to review.

bobbygryzynger’s picture

@jrockowitz Is is possible for classes that extend WebformElementBase to have these methods so that they can operate on themselves without explicitly attaching a handler? Or, is there a way you can suggest that these classes can attach a handler without the user having to do so manually?

bobbygryzynger’s picture

Status: Fixed » Active
jrockowitz’s picture

@bobby.gryzynger Not sure it makes sense to add theses hooks to the WebformElement plugin because most developers won't be creating their own elements or overriding existing one.

I think we can use my code to setup a custom hook_webform_update() to trigger events for elements that are created, updated, or deleted.

BTW, I think using entity hooks is the best way to "attach a handler without the user having to do so manually".

bobbygryzynger’s picture

@jrockowitz

I think we can use my code to setup a custom hook_webform_update() to trigger events for elements that are created, updated, or deleted.

Seems like a fine idea to me. If hook_webform_delete() could be called when deleteElement() is called that certainly takes care of my use-case.

jrockowitz’s picture

Status: Active » Needs review
FileSize
879 bytes

I think the below code is enough to point you in the right direction. You could easily change presave to delete. The attached patch fixes a minor issue.

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function webform_test_webform_presave(\Drupal\webform\WebformInterface $webform) {
  $elements_original = Yaml::decode($webform->getElementsOriginalRaw());
  $elements = \Drupal\Core\Serialization\Yaml::decode($webform->getElementsRaw());
  if ($elements_original != $elements) {
    $elements_original = \Drupal\webform\Utility\WebformElementHelper::getFlattened($elements_original);
    $elements = \Drupal\webform\Utility\WebformElementHelper::getFlattened($elements);

    // Handle create element.
    if ($created_elements = array_diff_key($elements, $elements_original)) {
      foreach ($created_elements as $element_key => $element) {
        drupal_set_message(t('Created @key', ['@key' => $element_key]));
      }
    }

    // Handle delete element.
    if ($deleted_elements = array_diff_key($elements_original, $elements)) {
      foreach ($deleted_elements as $element_key => $element) {
        drupal_set_message(t('Deleted @key', ['@key' => $element_key]));
      }
    }

    // Handle update element.
    foreach ($elements as $element_key => $element) {
      if (isset($elements_original[$element_key]) && $elements_original[$element_key] != $element) {
        drupal_set_message(t('Updated @key', ['@key' => $element_key]));
      }
    }
  }
}
bobbygryzynger’s picture

@jrockowitz This works for my use-case with the addition of:

    $elements_original = WebformElementHelper::getFlattened(
      is_array($elements_original) ? $elements_original : []
    );

    $elements = WebformElementHelper::getFlattened(
      is_array($elements) ? $elements : []
    );

Since getFlattened() is type-hinted for an array and Yaml::decode() can return null and will when an element is first created. Perhaps getElementsOriginalRaw() and getElementsRaw() should return an empty array when there are no elements?

jrockowitz’s picture

The attached patch adds a Webform::getElementsOriginalDecoded method to correspond with Webform::getElementsDecoded which can also return FALSE if the original elements were invalid but now we can just call...

$elements_original = $webform->getElementsOriginalDecoded() ?: [];
$elements = $webform->getElementsDecoded() ?: [];

  • jrockowitz committed 08367f2 on 8.x-5.x
    Issue #2879122 by jrockowitz: Create callback for question removal
    
bobbygryzynger’s picture

@jrockowitz I can confirm this last patch simplifies the logic required here.

jrockowitz’s picture

@bobby.gryzynger Can you please post a recipe for other people to learn from?

bobbygryzynger’s picture

@jrockowitz sure, which aspect did you have in mind? Performing additional processing using webform entity hooks?

jrockowitz’s picture

Maybe something like "How to track webform element create, update, and delete operations"

bobbygryzynger’s picture

@jrockowitz sure, I can do that. Feel free to assign this (or a new) issue to me.

jrockowitz’s picture

@bobby.gryzynger I can't reassign this issue to you but let's use this current issue.

jrockowitz’s picture

Status: Needs review » Fixed
bobbygryzynger’s picture

@jrockowitz I've added a cookbook recipe here: https://www.drupal.org/docs/8/modules/webform/webform-cookbook/how-to-tr... let me know if you think anything needs to be fleshed out a bit more.

Status: Fixed » Closed (fixed)

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