When the user deletes a node, mm_emfield_media_mover() gets called with $op='delete', which then calls mm_emfield_node_file_delete(), which then calls node_save().

function mm_emfield_media_mover($op, $action = null, $configuration = null, &$file = array(), $job = null, $nid = null) {
  if (module_exists('emfield')) {
    switch ($op) {
      ...
      case 'delete':
        if ($action == MM_EMFIELD_STORE || $action == MM_EMFIELD_COMPLETE) {
          mm_emfield_node_file_delete($configuration, $file);
        }
        break;
    }
  }

As a result, node_save() gets called while node_delete() is in the midst of executing. This causes a bunch of problems when a node is deleted, including:

  1. The emfield rows in content_field_myemfield, corresponding to the deleted node's harvested files, are not deleted. (The columns are instead set to NULL, because node_save() causes hook_nodeapi to get called with $op='update'.) See http://drupal.org/node/1497418
  2. Similarly, the rows in the content_field_myfilefield database table, corresponding to the deleted node's harvested files, are not deleted.
  3. Emfield outputs error message "unserialize() expects parameter 1 to be string". See http://drupal.org/node/1308856.
  4. Multiple calls to hook_nodeapi with $op='update', when user deletes node.

I modified media_mover_api.module so that mm_emfield_node_file_delete() is no longer called when a node is deleted. As a result, node_save() is no longer called in the midst of executing node_delete(), and all of the problems listed above went away.

The basic idea was to add an $op to hook_media_mover(), to distinguish between "user has deleted a node" ($op='node_delete') and "user has deleted a file" ($op='delete'). mm_emfield's implementation of hook_media_mover is then able to distinguish these two cases, and call mm_emfield_node_file_delete() only when the user has deleted a file, but not when the user has deleted a node. In this way, node_save() is no longer called in the midst of executing node_delete().

Because this approach distinguishes 'node_delete' from 'delete', it may be necessary to modify the implementation of hook_media_mover() in some other modules. For example, I found it was necessary to add a case statement for $op='node_delete' to mm_s3's implementation of hook_media_mover(), so that mm_s3_delete_file() would get called both when the user deleted a node and when the user deleted a file.

Here are the detailed code changes:

function media_mover_api_node_files_delete()
In the call to media_mover_api_file_delete_call(), I pass $op='node_delete' as a second parameter, to indicate that the user had deleted a node.

function media_mover_api_node_files_delete($node) {
     ...
      if ($configuration->settings->mma_node_item_delete) {
        foreach ($mm_files as $mm_file) {
           // media_mover_api_file_delete_call($mm_file); // original code
           media_mover_api_file_delete_call($mm_file, 'node_delete'); // <--- HERE.  added second parameter
        }
      ...
}

function media_mover_api_file_delete_call()
The function accepts a second parameter, which by default is 'delete', and passes this parameter to _media_mover_api_module_invoke().

function media_mover_api_file_delete_call($file, $op='delete') {
  foreach (media_mover_api_verbs() as $verb) {
    // _media_mover_api_module_invoke($configuration->{$verb}->module, 'media_mover', 'delete', $configuration->{$verb}->action, $configuration->{$verb}->configuration, $file, $configuration); // original code
  _media_mover_api_module_invoke($configuration->{$verb}->module, 'media_mover', $op, $configuration->{$verb}->action, $configuration->{$verb}->configuration, $file, $configuration); // <--- HERE.  replaced 'delete' with $op

  }

With the changes above, mm_emfield_media_mover() no longer calls mm_emfield_node_file_delete() when a node has been deleted.

function mm_s3_media_mover()
I modified mm_s3's implementation of hook_media_mover to support these changes.

function mm_s3_media_mover($op, $action_id = null, $configuration = array(), &$file = array()) {
   switch ($op) {
     case 'node_delete':  // <--- HERE.  Added this line, so mm_s3_delete_file() gets called when node is deleted
     case 'delete':
        mm_s3_delete_file($file, $configuration);
     break;
   }
}

Comments

kobnim’s picture

Issue summary: View changes

wrong link. fixed.

kobnim’s picture

StatusFileSize
new968 bytes
new800 bytes

On second thought, here is an alternate approach for fixing the problem, that does not require any changes to the hook_media_mover() API.

function mm_emfield_node_file_delete()

Add an optional parameter, $options, that tells this function whether the user has deleted the entire node, or just the individual file. If the user has deleted the entire node, then the function does not call node_save().

function mm_emfield_node_file_delete($configuration, $file, $options = array()) {
  ...
  if (empty($options['node_delete'])){ // user has deleted the entire node
    unset($node->{$field}[$key]);
    node_save($node);
  }
  ...
}

.

How $options gets passed to mm_emfield_node_file_delete():

(a) media_mover_api_node_files_delete() passes $options to media_mover_api_file_delete_call():

function media_mover_api_node_files_delete($node) {
	...
        $options = array('node_delete'=>TRUE); // node containing the file is in the process of being deleted
        foreach ($mm_files as $mm_file) {
	  media_mover_api_file_delete_call($mm_file, $options);
        }
        ...
}

(b) media_mover_api_node_files_delete() then passes $options to _media_mover_api_module_invoke(), which in turn passes $options to mm_emfield_media_mover():

function media_mover_api_file_delete_call($file, $options=array()) {
  ...
  foreach (media_mover_api_verbs() as $verb) {
    _media_mover_api_module_invoke($configuration->{$verb}->module, 'media_mover', 'delete', $configuration->{$verb}->action, $configuration->{$verb}->configuration, $file, $configuration, $options);
  }
  ...
}

(c) mm_emfield_media_mover() then passes passes $options to mm_emfield_node_file_delete():

function mm_emfield_media_mover($op, $action = null, $configuration = null, &$file = array(), $job = null, $nid = null) {
    ...
    switch ($op) {
      ...
      case 'delete':
        if ($action == MM_EMFIELD_STORE || $action == MM_EMFIELD_COMPLETE) {
	  $options = $nid; // When $op is 'delete', the sixth input parameter ($nid) is actually $options
	  mm_emfield_node_file_delete($configuration, $file, $options); 
        }
        break;
    }
    ...
}
kobnim’s picture

UPDATE / FYI:
I noticed a related problem that was not fixed by this patch.
If the user deletes a file from a filefield, and if mm_emfield_node_file_delete() gets called to clear the url from the corresponding emfield, then hook_nodeapi gets called twice with $op='update'.

The reason hook_nodeapi gets called twice with $op='update' is that mm_emfield_node_file_delete() calls node_save().

I don't see any easy way of fixing this problem. My only thought is to rearchitect things so that mm_emfield_node_file_delete() no longer needs to call node_save(), by calling mm_emfield_node_file_delete() from hook_nodeapi. But maybe it's not worth the trouble ...

kobnim’s picture

Issue summary: View changes

correction