The path_delete() function became a pile of crap when we added hook_path_delete().

For example, I create a node without a path alias. When I delete that node, path_node_delete() calls path_delete(array('source' => 'node/1')). Since path_load() returns FALSE, we get hook_path_delete(FALSE) invoked. WRONG

Now let's say I have a node with two aliases. When I delete that node, path_load() only returns the first alias. The other alias does not get deleted and a hook invoked for it...

Comments

dave reid’s picture

Assigned: Unassigned » dave reid
Status: Active » Needs review
Issue tags: +Needs tests, +redirect
StatusFileSize
new1002 bytes

And here's how we can fix it. This definitely needs tests.

Status: Needs review » Needs work

The last submitted patch, 1025904-path-delete-sucks.patch, failed testing.

geerlingguy’s picture

After applying the patch in #1, I get the following error:

PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '))' at line 1: DELETE FROM {url_alias} WHERE (pid IN ()) ; Array ( ) in path_delete() (line 473 of /home/~/domains/d7multisite/includes/path.inc).

This happens when trying to delete a node that doesn't have a path alias. This kinda makes the whole Redirect module useless right now for some of my sites ;-)

See: #1041726: Error during node deletion with redirect_path_delete()

dave reid’s picture

Status: Needs work » Needs review
StatusFileSize
new943 bytes

Yeah I also found my patch created an infinite loop. Yay! Revised patch that I've been using for a while now.

geerlingguy’s picture

Status: Needs review » Reviewed & tested by the community

Patch tested, works, is good. (Without #1041726: Error during node deletion with redirect_path_delete() applied, of course).

Do tests need to be written, though?

webchick’s picture

Status: Reviewed & tested by the community » Needs work

Yeah, tests would be good to have here. This is a bug we don't want to have twice. :)

alladdin’s picture

it works
thank you

RoboPhred’s picture

Status: Needs work » Needs review
StatusFileSize
new2.1 KB

I went to write a test for this, but I was unable to reproduce the error without this patch using drupal-7.0 or drupal-7.x-dev

My steps:
1. Created node
2. Added alias "alias1" to that node.
3. Added alias "alias2" to that node.
4. Deleted node

Upon deletion, both aliases are deleted.

Looking into the code, this is because the criteria for deleting the alias is the source path ("node/#"), which will match to both aliases. Under what conditions does this bug occur?

Attached is a web test for deleting a node with multiple aliases. As I have found, it should pass green without the original patch.

Asking bot to review test for point-in-case.

dave reid’s picture

You won't see any error because that's not what the bug is. You will need to:

1. Have a module that implements hook_path_delete() since no core module does
2. Delete a node that has no aliases at all - your hook_path_delete() gets passed a FALSE value when it should *not* be invoked in the first place
3. Delete a node that has more than one alias - your hook_path_delete() will only get invoked for one of the aliases, not both when it should get invoked for both

RoboPhred’s picture

Thanks for the clarification. Thats significantly more complex than any of the test cases I have seen so far. Is there any standard of testing hooks? I will look around for any previous cases.

Edit: Found http://drupal.org/node/302577, although it doesn't state how to test the hook, and to call the test functions I need to somehow give the module the class...

Also, is there any reason to include or ignore the test I just wrote? Deletion of a single alias is tested, but not multiple. Would including this test be prudent, or bloat?

bfroehle’s picture

RoboPhred:

You can create a modules/path/tests directory and put a custom 'path_test' module there. Make sure to set hidden=TRUE in the path_test.info file. The module is enabled in the path.test routines using the setUp function.

Look in modules/filter/tests, or modules/node/tests, etc to see how to do it.

RoboPhred’s picture

StatusFileSize
new2.52 KB

Current progress on adding tests. This patch is test only, and does NOT include the original.

Currently, the test for node deletion fails as it should. However, I am having issues with assertText not finding text that was inserted with drupal_set_message under some conditions... While the mock module outputs a message (confirmed by the test output), the actual test fails to find it. Someone please review this, its probably a mistake on my part, but I have been unable to find it so far.

Status: Needs review » Needs work

The last submitted patch, 1025904-path-hooks-test.patch, failed testing.

bfroehle’s picture

Your patch will need to include the contents of modules/path/tests as well.

RoboPhred’s picture

Status: Needs work » Needs review
StatusFileSize
new3.25 KB

Thought that was being included. Looks like cvs diff won't actually pick up the contents of new files, while tortoiseCVS patch allows new files but garbles the file names and doesn't use the unified format. Until I track down a better way, here is tortoise's patch with the file paths modified.

Status: Needs review » Needs work

The last submitted patch, 1025904-path-hooks-2.patch, failed testing.

RoboPhred’s picture

StatusFileSize
new3.41 KB

Thought not. Turns out the d.org patch page on the handbook has info on this case that I overlooked before, this one should work.

RoboPhred’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 1025904-path_hooks_tests_3.patch, failed testing.

RoboPhred’s picture

Ok, so the "hook_path_delete not called" failure is expected as this patch does not include the fix, making hook_path_delete output "hook_path_delete called" in the menu. However, the other three should not be failing. The text they are looking for (such as "hook_path_delete: node/1 => pRrZo4pJ") are output in the messages area right next to the hook_path_delete message which is being identified as it should be. As far as I can tell this is a bug in the testing framework, but its possible its interpreting the text differently or there's some special sequence in there i'm not noticing. Can anyone confirm?

bfroehle’s picture

Assigned: dave reid » bfroehle

I think you have to use assertRaw() to get at text not in the content region. I'll upload a revised patch soon.

bfroehle’s picture

Assigned: bfroehle » Unassigned
Status: Needs work » Needs review
StatusFileSize
new4.91 KB
new4.16 KB

Two patches. The first is only the testing routines. The second incorporates Dave Reid's fix in #4.

Hopefully the test routines are easy enough to read -- they built upon RoboPhred's start in #17.

Anonymous’s picture

Title: path_delete() can invoke hook_path_delete() even if no URL aliases were deleted, cannot handle multiple aliases » multiple aliases editing option reqd

There should be an option to edit multiple aliases - there should be a new filed for entering new url alias - and save by clicking the save button only once so that all edited aliases update at a time. editing each alias is really time consuming and irritating. recently i changed some aliases of http://vision4life.in and it consumed a lot of time.

bfroehle’s picture

Title: multiple aliases editing option reqd » path_delete() can invoke hook_path_delete() even if no URL aliases were deleted, cannot handle multiple aliases
Version: 7.x-dev » 8.x-dev
Issue tags: -Needs tests, -redirect +Needs backport to D7

@pvm610: Please open a new issue as a feature request for your comment in #23. Thanks!

Kjartan’s picture

StatusFileSize
new4.97 KB

Rerolled the patch to the new core/ paths.

Status: Needs review » Needs work
Issue tags: -Needs backport to D7

The last submitted patch, 1025904-22-tests-and-fix.patch, failed testing.

jhedstrom’s picture

Issue summary: View changes

I think this may be resolved in 8.0.x. Logic has moved to AliasStorage::delete(), and also the path field type, PathItem. From AliasStorage::delete():

  /**
   * {@inheritdoc}
   */
  public function delete($conditions) {
    $path = $this->load($conditions);
    $query = $this->connection->delete('url_alias');
    foreach ($conditions as $field => $value) {
      $query->condition($field, $value);
    }
    $deleted = $query->execute();
    // @todo Switch to using an event for this instead of a hook.
    $this->moduleHandler->invokeAll('path_delete', array($path));
    return $deleted;
  }
David_Rothstein’s picture

That looks like the same logic in Drupal 8 as Drupal 7, so won't it have the same problem?

Note that #2637570: path_delete() tries to delete non-existing path, leads to flushing whitelist cache was recently filed which is related to this also (and is a possible duplicate).

David_Rothstein’s picture

David_Rothstein’s picture

Priority: Normal » Major
Issue tags: +Performance

Adding the "Performance" tag and moving to major priority, based on discussion in that issue. (The performance aspect of this might only apply to Drupal 7 though.)

catch’s picture

#1209226: Avoid slow query for path alias whitelists which is already applied to 8.x and needs review for 7.x gets rid of the slow query after a cache clear. However afaik the cache clear will still happen in 8.x.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

davidwhthomas’s picture

Just got bitten by this issue with hook_path_delete and $path being empty. What needs doing on this one?

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

berdir’s picture

Version: 8.6.x-dev » 7.x-dev
Issue tags: -Needs backport to D7

Changing back to 7.x as this was fixed as part of the path_alias entity conversion.

klausi’s picture

Ran into super slow node deletes today on a site with entity_translation module enabled. It implements path_entity_translation_delete() and tries to delete non-existing paths, which then triggers a slow drupal_path_alias_whitelist_rebuild() down the line. This patch fixes path_delete() to not call hooks on non-existing paths, will test that.

  1. +++ b/core/includes/path.inc
    @@ -464,14 +464,13 @@ function path_delete($criteria) {
    +  $paths = array();
    

    unused variable that should be removed.

  2. +++ b/core/modules/path/path.test
    @@ -546,3 +546,62 @@ class PathMonolingualTestCase extends PathTestCase {
    +class PathHooksTestCase extends DrupalWebTestCase {
    

    this test seems a bit complicated, we don't actually need to test the UI? We could just set variables when hooks are called. We don't even need to create nodes, we could just test with the /user path for example.

  3. +++ b/core/modules/path/path.test
    @@ -546,3 +546,62 @@ class PathMonolingualTestCase extends PathTestCase {
    +  function setUp() {
    

    public function

  4. +++ b/core/modules/path/path.test
    @@ -546,3 +546,62 @@ class PathMonolingualTestCase extends PathTestCase {
    +  function testPathHooks() {
    

    public function

klausi’s picture

Status: Needs work » Needs review
StatusFileSize
new758 bytes

Here is a rerolled patch. I'm a bit afraid of the endless while loop that could potentially run many iterations if there are many path aliases or somebody calls the function wrong. If a developer calls path_delete(array());by accident then it will wipe out your whole URL alias table!

On the other hand path_delete() currently also wipes out the url_alias table if called with en empty array, so the while loop might actually be ok?

Does not include the test case yet since I'm not really happy with it.

klausi’s picture

Related issue is #1209226: Avoid slow query for path alias whitelists as catch mentioned. The performance problem is less often hit with his patch here, so we should do both.

klausi’s picture

StatusFileSize
new758 bytes

Noticed that clearing caches should happen AFTER the delete query. Invoking hooks should be done before the delete query, we do it the same way as with node_delete() for example.

donquixote’s picture

Invoking hooks should be done before the delete query, we do it the same way as with node_delete() for example.

Indeed this is the case for hook_node_delete().
For paths, until now, the hook was invoked _after_ the deletion.
This could be considered a BC break. But since this hook was already broken to begin with, perhaps it is ok?

The only implementation I am currently aware of is redirect_path_delete().
Older versions of redirect had this code commented out, but it was re-introduced in #1763436: redirect_path_delete() code is commented, not creating redirect on alias delete.
The code in redirect itself seems like it does not care whether it is called before or after the alias is deleted.
But there could be modules which react to e.g. hook_redirect_insert() and do something with aliases.

I think we should at least provide good explanation why the hook should be called before the delete operation.

Or, if we want to maintain maximum BC, we could introduce an additional hook_path_pre_delete() which runs before, and keep hook_path_delete() running after.

donquixote’s picture

Here is a rerolled patch. I'm a bit afraid of the endless while loop that could potentially run many iterations if there are many path aliases or somebody calls the function wrong.

We can get an infinite loop, if one hook implementation creates new paths which match the criteria for deletion.
To prevent this, we should load all matching paths at once, or (to save memory) iterate through a single select query instead of calling path_load().

path_load() is flawed in that it only ever loads the first matching result.
There could be an alternative to path_load() with generator syntax - or something equivalent for older PHP versions.

donquixote’s picture

For paths, until now, the hook was invoked _after_ the deletion.
This could be considered a BC break. But since this hook was already broken to begin with, perhaps it is ok?

A silly example would be a module that implements hook_path_delete() to report the number of paths left in the database.
With this change, this would produce a different result.
Interestingly, this implementation would not be "broken" with the current implementation, because it would not care about the $path parameter being sent to it.

klausi’s picture

StatusFileSize
new989 bytes

Thanks for the input, I think you are right.

New patch:
1) Query all paths with a SELECT first. That way we don't do 1 query per while loop iteration and this is faster. It also prevents theoretical infinite loops.
2) Invoke the hook AFTER the DB record for the path was deleted to preserve maximum backwards compatibility.

klausi’s picture

Title: path_delete() can invoke hook_path_delete() even if no URL aliases were deleted, cannot handle multiple aliases » path_delete() cannot handle multiple aliases
Status: Needs review » Needs work

This patch does not apply anymore after #3084980: Trying to access array offset on value of type null in path_delete().

Our performance issue I mentioned is solved with that other issue, so we don't need this patch anymore. I think this issue is still useful for people that want to look into the problem that the hook is only invoked once even if multiple aliases have been deleted.

Status: Needs work » Closed (outdated)

Automatically closed because Drupal 7 security and bugfix support has ended as of 5 January 2025. If the issue verifiably applies to later versions, please reopen with details and update the version.