Wednesday, July 27, 2016

Digging Deeper into WordPress Hooks and Filters

WordPress comes loaded with a series of hooks and filters that let you hook into specific parts of when WordPress operates. For example, you can attach a custom function so that it executes when the WordPress save_post action is called, giving you access to the post being saved.

Plugins and themes are an area where hooks are in abundance. Any decent plugin or theme will come with a range of these hooks that they either use internally to operate or expose externally for developers. When a plugin or theme isn't exposed enough you might have issue customizing how it operates.

wordpress hooks banner

Generally speaking, the plugins and themes you will run into will be based around Object Orientated Design methods, where one or more classes define how the functionality works. Inside these classes they might define hooks that you want to remove entirely or replace with your own custom functions.

Today were going to dig into WordPress hooks and how you can handle them both in a traditional sense and also when dealing with classes or objects.

Removing Standard Hooks

When hooks and filters are not inside a class they are fairly straight-forward to remove. To remove these hooks you need to use the remove_action function. The basics of this are that you need to supply the following:

  • $tag – The name of the hook to which the function is attached to.
  • $function_to_remove – The name of the function that should be removed.
  • $priority – The priority of the function when it was attached to the hook.
  • When actions hooks are created with do_action it will trigger all functions that have been added to it with add_action as soon as WordPress reaches it.

    Example 1: Standard WooCommerce Hook

    Let's look at a visual example. Consider the WooCommerce plugin. When you're on a single page (using the single-product template) you will by default see the breadcrumb bar.

    Basic WordPress hook WooCommerce

    This is outputted via the do_action( 'woocommerce_before_main_content' ); hook. It's purpose is to output the starting content wrapper and the breadcrumbs for the product.

    WooCommerce hook photo

    Since we know the name of the action and the function name attached to that hook, we can remove the function entirely as follows.

    //remove breadcrumbs from single product remove_action('woocommerce_before_main_content', 'woocommerce_breadcrumb', 20);

    Now the breadcrumbs will disappear. As long as you know the name of the hook, the name of the function you want to remove and the priority you can remove and replace functions as you see fit. The priority of the hook is important as it has to match the priority used when the function was originally hooked.

    basic hook WooCommerce removed

    Removing Hooks Added Inside Classes

    When you have actions and filters defined inside classes it can be a little bit tricky. Even though you might know the name of the hook and the name of the function, you won't be able to use remove_action as we did before.

    The issue is that if a plugin or theme defines these actions inside of class, those hooks are now attached inside the class. To remove them you will need to pass in the class variable to the remove_action function as follows:

    //remove a hooked function from within a call function('hook_name', array($myclass, 'my_function_remove'), 10); So Where Exactly Is the Class Object?

    This often is the trickiest part. To remove your function you need the class object. To find this you will need to hunt through either the documentation for the plugin or theme (if there is anyway) or alternatively (and less excitingly) searching through their source code to find the variable name.

    If the class is a Singleton class, meaning that there should be only one of them at a single time, then you should be able to get the class from using one of the classes inbuilt functions to return the instance of the class such as:

    $myClassObject = MyClass::getInstance()

    If the class doesn't have a method like that you can try and get the object by calling it globally such as:

    global $myClassObject

    Since each developer develops differently you might have to search to find the variable you need. Hopefully, if the class has been structured correctly, you should be able to get the object.

    If you're trying to remove visual elements outputted you should be able to trace back where it all comes from, for example:

  • If you're trying to remove some text, inspect the element and see if it has any classes or ID's.
  • If you have something to search on you can open up your theme or plugin and search for where those classes or ID's are uses, they most likely will be wrapped in some function.
  • If you've found a function that causing the output, try and see if it's hooked with add_action anywhere.
  • If you're successful you should have the name of the hook and the name of the function you want to remove. All you need is the object itself and you're good to go!

    Example 1: Hook Inside WooCommerce Class

    Let's look at a practical example so you can see how you'd apply this technique.

    Inside of WooCommerce the WC_Emails class is used to generate the output for emails that are sent out (such as the new, processing and completed orders). Inside of WC_Emails the constructor attaches all of the various functions onto hooks to build the output as seen below.

    advanced class hook screenshot

    When displaying an email WordPress will hit the woocommerce_email_customer_details hook and will trigger all functions attached to it. When this happens it will trigger the customer_details function defined in the WC_Emails class, outputting the customer details.

    advanced class hook details

    If we didn't want to display these details we would have to remove the customer_details function from the class.

    Luckily, at the bottom of the constructor is the woocommerce_emails action that's being called with do_action. We can hook onto there so we can remove our customer details. This hook is passed the object itself via $this so it's perfect for our needs.

    //Remove email details from the `WC_Emails` class function remove_customer_email_details($instance) { //remove `customer_details` hooked function remove_action('woocommerce_email_customer_details', array( $instance, 'customer_details'), 10); } add_action('woocommerce_email', 'remove_customer_email_details');

    We call remove_action and pass in both the $instance and the customer_details function and when we view our emails the customer details have vanished, removed entirely.

    advanced class hook details removed

    In this scenario, WooCommerce left a perfect location for us to hook onto so we could remove these functions. It's up to the plugin or theme developer to place these handy hooks in locations where other developers may need them. That being the case you might struggle removing some functions if the developers haven't thought about it.

    Example 2: Hook Inside a Custom Class

    With WooCommerce they have a dedicated team of great developers plus a series of volunteers or contributors continually working to improve their plugin. They've gone through years of development so they know the importance of proving the right hooks so developers can customize things.

    In this example, we're going to look at just a simple situation. The idea behind this example is that you can't be guaranteed you will have hooks to rely on and if the developer is even thinking about people extending their work. In this scenario, we're working between a parent and child theme. Inside the parent theme we have our main class and in our child theme we have our additional function.

    NOTE: It's useful to point out that remove_filter is the same as remove_action. At the end of the day, they are all a way to removed hooked function and WordPress is happy for you to use them interchangeably.

    //A simple test class, loaded directly in your website //Loaded from the parent theme class testClass{ //magic constructor public function __construct(){ add_filter('the_title', array($this, 'wrapTitle'), 10, 1); add_filter('add_extra_word', array($this, 'output_word'), 10, 1); } //wrap the title for each post public function wrapTitle($title){ $html = ''; $html .= '<div class="titleWrap">'; $html .= $title; //append a word to the end of the title $html = apply_filters('add_extra_word', $title); $html .= '</div>'; return $html; } //append the word 'Hello World' to the end of the title public function output_word($title){ $html = ''; $html = $title . ' <strong>Hello World</strong>'; return $html; } } $testclass = new testClass();

    This class adds a function called wrapTitle onto the the_title hook right in the _construct method. This means that as soon as the class is loaded it will hook that function onto the title and be called every time we're displaying a post. Inside of the wrapTitle function it wraps the current title around a div and then calls the add_extra_word filter.

    Back in the constructor we hooked another function called output_word onto the add_extra_word filter. This functions purpose is to add the bolded Hello World text to the end of the title.

    hook example two before

    As you can see this isn't very attractive. We want to remove this added word from the end of the title. This is the perfect opportunity to remove the function added in the class. Here's how we do it:

    //removes the extra word appended to the title (added by the class) //Added to your child theme function remove_extra_word(){ global $testclass; remove_filter('add_extra_word', array($testclass, 'output_word'), 10); } add_action('init', 'remove_extra_word');

    We first hook our function to be loaded on init. The reason we do this instead of just executing the remove_filter right in the PHP file is because we want it to trigger only once WordPress has started loading. If we declared this function before the class was even loaded it would do nothing (as the class needs to exist first).

    We grab a copy of the class by using global $testclass. We do this because this class didn't have any way of giving us our object directly.

    Inside the function we now use the remove_filter function and remove the output_word function attached to the add_extra_word action defined in our class.

    When this is all done and dusted the Hello World text will be removed and your titles will be back to normal.

    hook example two after

    We managed to do all that just by adding our own custom function and removing our hooked actions.

    Where This Technique Won't Work

    If there is no way for you to find the object of the class at all (either passed in as a variable in a hook or via some other method) then this technique will fail. A core part of Object Orientated Design principals says that there should always be a reference to the object so that it can be accessed, or failing that an easy way to load the object.

    If this happens you can try the following steps (each more drastic and less recommended as we go down):

  • Contact the theme or plugin developer and ask for additional hooks.
  • Most developers are pretty keen to help people out so it's a good place to start. When they update their plugin or theme you should be able to seamlessly update (if it's been set up correctly).
  • Change your plugin or your theme.
  • If it's not working for you, see if there is anything else out there that might provide better functionality and be more developer friendly.
  • Manually edit your plugin or theme directly.
  • If you must stick with your plugin or theme and you desperately need to change the way it works, then you can always edit the files directly. Generally speaking this is an awful suggestion as any changes you make will be lost if you ever update your theme or plugin. However, sometimes you just need to get a problem solved.
  • Wrapping It All Up

    Hopefully after all this you should have a strong grasp on how to use WordPress's hook system to remove and adjust functionality inside your themes and plugins. If you're patient enough and can track down the hook, function and the class object you can remove and replace functionality as you see fit.

    You might want to examine some of your currently installed plugins to see how developer friendly they are. Do they supply a wide range of hooks and filters? Are any of their values or settings explicitly hard coded in their theme or plugin? This information is important if you want to be able to extend and customize your website as you see fit.

    For some further reading on the WordPress hook system check out Demystifying the WordPress Hook System by Agbonghama Collins.


    Source: Digging Deeper into WordPress Hooks and Filters

    No comments:

    Post a Comment