Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removing hooks #2701

Open
Tracked by #2044
jonathanbossenger opened this issue Jul 19, 2024 · 13 comments
Open
Tracked by #2044

Removing hooks #2701

jonathanbossenger opened this issue Jul 19, 2024 · 13 comments
Assignees

Comments

@jonathanbossenger
Copy link
Collaborator

jonathanbossenger commented Jul 19, 2024

Details

  • Content type (Online Workshop, Lesson, Course, Tutorial, or Lesson Plan): Lesson
  • Content title: Removing hooks
  • Topic description: How to remove callback functions from firing on specific hooks
  • Audience (User, Developer, Designer, Contributor, etc.): Developer
  • Experience Level (Beginner, Intermediate, Advanced, Any): Intermediate

Prerequisites

It is assumed that the learner has already completed the following lessons:

Learning Objectives

  • Explain why you might want to remove a callback function from a hook
  • Demonstrate an example of removing a callback function from a hook
  • Demonstrate how to remove all callbacks from a specific hook

Related Resources and Other Notes

Automation Code

//lesson

Copy link
Contributor

Lesson Development Checklist

  • Gather any relevant links to Support, Docs, or related material
  • Description and Objectives finalized
  • Lesson created and announced to the team for review
  • Lesson reviewed
  • Lesson video submitted and published to WPTV
  • Lesson created on Learn.WordPress.org
  • Lesson video published to YouTube
  • Lesson on Learn.WordPress.org updated with YouTube video
  • Lesson published to Learn.WordPress.org

2 similar comments
Copy link
Contributor

Lesson Development Checklist

  • Gather any relevant links to Support, Docs, or related material
  • Description and Objectives finalized
  • Lesson created and announced to the team for review
  • Lesson reviewed
  • Lesson video submitted and published to WPTV
  • Lesson created on Learn.WordPress.org
  • Lesson video published to YouTube
  • Lesson on Learn.WordPress.org updated with YouTube video
  • Lesson published to Learn.WordPress.org

Copy link
Contributor

Lesson Development Checklist

  • Gather any relevant links to Support, Docs, or related material
  • Description and Objectives finalized
  • Lesson created and announced to the team for review
  • Lesson reviewed
  • Lesson video submitted and published to WPTV
  • Lesson created on Learn.WordPress.org
  • Lesson video published to YouTube
  • Lesson on Learn.WordPress.org updated with YouTube video
  • Lesson published to Learn.WordPress.org

@jonathanbossenger
Copy link
Collaborator Author

@CrochetFeve0251 Let me know if you would like to continue with drafting a script for this next lesson in the module.

@CrochetFeve0251
Copy link

Script

Introduction

Sometimes when doing some compatibility with another plugin, some logic added by another plugin or a theme is creating an incompatibility.
In that situation there is little we can do except to remove the incompatible callback and add a new one with some compatible logic.

Removing a callback from a hook

To remove a callback, we have two functions based on the hook type.

Filter

In the case of a filter, we can use the function remove_filter.

To understand further how this works, let's take an example.

Imagine I got the function my_callback as a callback from the filter my_filter with priority 12.

function my_callback() {

}

add_filter('my_filter', 'my_callback', 12);

To remove that callback, I will have to call the function remove_filter with the following arguments:

remove_filter('my_filter', 'my_callback', 12);

Action

In the case of an action, we can use the function remove_action.

To understand further how this works, let's take an example.

Imagine I got the function my_callback as a callback from the action my_action with priority 12.

function my_callback() {

}

add_filter('my_action', 'my_callback', 12);

To remove that callback, I will have to call the function remove_action with the following arguments:

remove_action('my_action', 'my_callback', 12);

Removing all callbacks for a hook

First, I need to warn you doing this is not recommended because it can remove important hooks for certain plugins or themes, and it is more recommendable to remove only the callback which creates an incompatibility.

If you want to remove all callbacks from an action, you can use the method remove_all_actions:

remove_all_actions('my_action');

For a filter you can use remove_all_filters:

remove_all_filters('my_filter');

It is also important to note that these methods should not be called from inside the hook you want to remove otherwise this would result in an infinite loop.

@CrochetFeve0251
Copy link

@jonathanbossenger I didn't add much more than what is in the documentation as I don't see much more information that would be useful for that section which is not in the documentation.

@jonathanbossenger
Copy link
Collaborator Author

Sometimes those are the easiest lessons to script 😁

I will review this week.

@jonathanbossenger
Copy link
Collaborator Author

jonathanbossenger commented Sep 4, 2024

Edited Script

Removing Hooks

Introduction

While the WordPress hooks system makes WordPress very extendable, it can sometimes create incompatibilities with your plugin’s execution.

In this lesson you will learn about removing hook callback functions.

Example

Let’s say you are developing a plugin that will add some a copyright text string with a date to the end of every page on a WordPress site. Your plugin code might look something like this:

<?php
/**
 * Plugin Name: Add Copyright
 * Description: Add Copyright with current year to all Pages
 * Version: 1.0
 * Author: Jon Doe
 */

namespace JonDoe\AddCopyright;

add_filter( 'the_content', __NAMESPACE__ . '\add_copyright' );
function add_copyright( $content ) {
	if ( ! is_page() ) {
		return $content;
	}
	$year = date( 'Y' );

	return $content . "<p>&copy; $year</p>";
}

You receive a support request from a user who is using your plugin, complaining that there is some other text being added to the end of the page content that is not related to your plugin.

You investigate and find that another plugin is adding some text to the end of the page content based on an Extra Option setting.

You realize that the other plugin is adding the extra text to the end of the page content using the same filter hook as your plugin.

<?php
/*
Plugin Name: WP Learn Extra Content
Version: 1.0.0
*/

namespace WP_Learn\Extra_Content;

add_action('admin_init', __NAMESPACE__ . '\add_option');
function add_option() {
	add_settings_field('wp_learn_extra_option', 'Extra Option', __NAMESPACE__ . '\extra_option_field', 'general');
	register_setting('general', 'wp_learn_extra_option');
}
function extra_option_field() {
	echo '<input name="wp_learn_extra_option" id="wp_learn_extra_option" type="text" value="' . get_option('wp_learn_extra_option') . '" />';
}

add_filter( 'the_content', __NAMESPACE__ . '\add_extra_option' );
function add_extra_option( $content ) {
	$extra_option = get_option('wp_learn_extra_option');
	if ( ! $extra_option ) {
		new \WP_Error( 'wp_learn_extra_option', 'Extra content is empty.' );
		return $content;
	}
	$content .= '<p>' . $extra_option . '</p>';
	return $content;
}

The plugin user wants only the copyright text to be displayed on pages, but to retain the other plugin’s functionality on all other post types.

In that situation there is little you can do except to somehow remove the incompatible callback function hooked into the same filter.

Removing a callback from a hook

Fortunately, WordPress allows you to remove a hooked callback, depending on the hook type.

Filter

In the case of a filter, you can use the remove_filter() function.

In order to remove a callback hooked into a filter, we call the function, passing the filter name and the callback function name to remove.

remove_filter( 'the_content', 'WP_Learn\Extra_Content\add_extra_option' );

If the callback function was added with a priority, you can also pass the priority as the third argument.

remove_filter( 'the_content', 'WP_Learn\Extra_Content\add_extra_option', 10 );

In this specific case, the copyright text should only be displayed on pages, so you should only remove the other plugin’s callback function when the content being rendered is actually page.

if ( ! is_page() ) {
    return;
}
remove_filter( 'the_content', 'WP_Learn\Extra_Content\add_extra_option' );

Ideally, you should also remove any callbacks at the right point in request execution. In this specific case, a good place to remove the callback would be at the wp action hook, which is executed after the query_posts() function, which sets up the query loop.

add_action( 'wp', 'custom_remove_extra_option_hook' );
function custom_remove_extra_option_hook() {
	if ( ! is_page() ) {
		return;
	}
        remove_filter( 'the_content', 'WP_Learn\Extra_Content\add_extra_option' );
}

This code could be added to a custom plugin, or a child theme’s functions.php file.

Action

In the case of an action, you can use the remove_action() function.

As with removing a filter, you call the function, passing the action name and the callback function name to remove.

remove_action( 'admin_init', 'hooked_callback_function' );

Again, if the callback function was added with a priority, you can also pass the priority as the third argument.

remove_action( 'admin_init', 'hooked_callback_function', 10 );

Removing all callbacks for a hook

It is also possible to remove all callbacks for a specific hook.

Doing this is not generally recommended as it can remove important hooks for certain plugins or themes, and it is better to remove only specific hook callbacks which create incompatibilities.

If you do want to remove all callbacks from an action, you can use the remove_all_actions() function.

To remove all callbacks from a filter you can use the remove_all_filters() function:

For both functions you pass the hook name as the first argument, and optionally the hook priority as the second.

It is also important to note that these functions should not be called from inside the hook you want to remove the callbacks from, otherwise this would result in an infinite loop.

@jonathanbossenger
Copy link
Collaborator Author

@CrochetFeve0251 thanks for your first draft. I've edited the script to include an example of the type of conflict the script is talking about, using the example plugin from the lesson on Naming Collisions. I've also added links to the various function references.

Let me know if you're happy with the edited version or if you have any further edits/suggestions.

Once you're happy with it, we can pass it on to a video creator.

@CrochetFeve0251
Copy link

CrochetFeve0251 commented Sep 4, 2024

@jonathanbossenger inside the example code I see that:

if ( ! $extra_option ) {
		new \WP_Error( 'wp_learn_extra_option', 'Extra content is empty.' );
		return $content;
	}

I am wondering if you were meaning that?

if ( ! $extra_option ) {
		return new \WP_Error( 'wp_learn_extra_option', 'Extra content is empty.' );
	}

For that example:

add_action( 'wp', 'custom_remove_extra_option_hook' );
function custom_remove_extra_option_hook() {
	if ( is_page() ) {
		remove_filter( 'the_content', 'WP_Learn\Extra_Content\add_extra_option' );
	}
}

Maybe we would like to show them logic where they bail out early on rather than putting the main logic inside the condition:

add_action( 'wp', 'custom_remove_extra_option_hook' );
function custom_remove_extra_option_hook() {
	if ( ! is_page() ) {
		return;
	}
        remove_filter( 'the_content', 'WP_Learn\Extra_Content\add_extra_option' );
}

Last but not least I don't see anymore the example for removing all hooks, did you remove it or you forgot to add it back?

@jonathanbossenger
Copy link
Collaborator Author

jonathanbossenger commented Sep 5, 2024

if ( ! $extra_option ) {
		new \WP_Error( 'wp_learn_extra_option', 'Extra content is empty.' );
		return $content;
	}

I am wondering if you were meaning that?

if ( ! $extra_option ) {
		return new \WP_Error( 'wp_learn_extra_option', 'Extra content is empty.' );
	}

No, I had intended the code that is there so that it throws the error gracefully, but the code execution still continues, as you need to return the $content variable for the the_content filter. I don't think plugins should break user's websites.

For that example:

add_action( 'wp', 'custom_remove_extra_option_hook' );
function custom_remove_extra_option_hook() {
	if ( is_page() ) {
		remove_filter( 'the_content', 'WP_Learn\Extra_Content\add_extra_option' );
	}
}

Maybe we would like to show them logic where they bail out early on rather than putting the main logic inside the condition:

add_action( 'wp', 'custom_remove_extra_option_hook' );
function custom_remove_extra_option_hook() {
	if ( ! is_page() ) {
		return;
	}
        remove_filter( 'the_content', 'WP_Learn\Extra_Content\add_extra_option' );
}

Yup, I agree, I updated the code example.

Last but not least I don't see anymore the example for removing all hooks, did you remove it or you forgot to add it back?

I actually left that out on purpose, in the video we can show the link to the documentation, but since it's generally recommended anyway, I didn't think that adding examples of how to use it were of any benefit to the lesson. But I'm happy to include a note about it for "FYI" purposes

@jonathanbossenger
Copy link
Collaborator Author

@CrochetFeve0251 replied to your feedback (thanks) let me know if you have any other feedback on my replies.

@MichelleBlanchette
Copy link

Hello! I was just reviewing the conversation here and think an important concept is missing for the advanced plugin developer: preventing infinite loops.

This is actually most often the reason I want to remove a hook, and I've often seen it done in plugins like WooCommerce, for instance, where a hook will be removed, some logic will be performed, and then the hook will be immediately added back.

Is this a code pattern worth discussing in this lesson?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants