Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/wp-includes/class-wp-hook.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,16 @@ private function resort_active_iterations( $new_priority = false, $priority_exis
}
}

/*
* If $current's bucket was emptied during iteration, the iterator now
* sits on the first remaining priority greater than $current. The
* trailing next() in ::apply_filters() would skip past it, so step
* back one so that next() lands on it instead.
*/
if ( false !== current( $iteration ) && ! isset( $this->callbacks[ $current ] ) ) {
prev( $iteration );
}

// If we have a new priority that didn't exist, but ::apply_filters() or ::do_action() thinks it's the current priority...
if ( $new_priority === $this->current_priority[ $index ] && ! $priority_existed ) {
/*
Expand Down
38 changes: 38 additions & 0 deletions tests/phpunit/tests/hooks/removeFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,44 @@ public function test_remove_filter_with_another_at_different_priority() {
$this->check_priority_exists( $hook, $priority + 1, 'Should priority of 3' );
}

/**
* Removing the last callback at the currently iterating priority must not
* cause the next remaining priority to be silently skipped.
*
* @ticket 65167
*
* @covers WP_Hook::remove_filter
* @covers WP_Hook::apply_filters
*/
public function test_remove_filter_during_iteration_does_not_skip_next_priority() {
$hook = new WP_Hook();
$fired = array();

$early = static function ( $value ) use ( &$fired ) {
$fired[] = 'early';
return $value;
};

$self_removing = static function ( $value ) use ( &$hook, &$self_removing, &$fired ) {
$fired[] = 'self_removing';
$hook->remove_filter( __FUNCTION__, $self_removing, 10 );
return $value;
Comment thread
obenland marked this conversation as resolved.
Outdated
};
Comment thread
obenland marked this conversation as resolved.
Outdated

$later = static function ( $value ) use ( &$fired ) {
$fired[] = 'later';
return $value;
};

$hook->add_filter( __FUNCTION__, $early, 5, 1 );
$hook->add_filter( __FUNCTION__, $self_removing, 10, 1 );
$hook->add_filter( __FUNCTION__, $later, 20, 1 );

$hook->apply_filters( null, array( null ) );

$this->assertSame( array( 'early', 'self_removing', 'later' ), $fired );
}

protected function check_priority_non_existent( $hook, $priority ) {
$priorities = $this->get_priorities( $hook );

Expand Down
Loading