5
Hooks

UNDERSTANDING HOOKS

In WordPress, hooks are the primary feature of the Plugin API. They enable plugin developers to “hook” into the WordPress flow and alter how it works without modifying the core code. By not altering core WordPress, users can upgrade to newer versions without losing any modifications that their plugins have done.

Plugins would have no way to modify how WordPress works without hooks. In many ways, hooks represent what plugins are. The lessons you learn in this chapter are essential for utilizing the techniques that you'll learn in later chapters. Without hooks, the plugins you build would have no way to modify how WordPress functions.

WordPress has two primary types of hooks: action hooks and filter hooks. Action hooks allow you to execute a function (action) at a certain point in the WordPress flow. Filter hooks allow you to manipulate (filter) output passed through the hook.

ACTIONS

Action hooks enable you to execute code at a specific moment in the WordPress loading process. It's easiest to think of these as “events” that happen at certain stages. By attaching a function to the action hook, your plugin is telling WordPress it wants to do something at this event.

The do_action() function in WordPress creates and fires an action hook. When attaching an action to the hook, you wouldn't call this function directly. However, there may come a point where you will want to create custom action hooks for your plugin, which you will learn about in the “Creating Custom Hooks” section of this chapter.

<?php
do_action( $tag, $arg = '' );

The function accepts two parameters.

  • $tag: The name or identifier for the action hook.
  • $arg: Value(s) passed to registered actions. This is where things get tricky for first‐time developers. This parameter may be split into any number of parameters, which will depend on the specific action hook.

The following is an example of what an action hook would look like with multiple parameters:

<?php
do_action( $tag, $arg_a, $arg_b, $arg_c );

One of the most common hooks in WordPress is the wp_head action hook. It is fired within the opening <head> and closing </head> tags on the frontend of the website. It's a useful hook for SEO plugins to add meta and Open Graph social media tags. Take a look at this action hook:

<?php
do_action( 'wp_head' );

As you can see, this particular action hook passes no parameters. Often, this is the case with action hooks. When this code fires in WordPress, it looks for every action registered to the wp_head hook. It then executes each in order of priority. The page load process continues after every action has completed its execution.

Now look at an action hook that passes extra arguments to any actions attached to it.

<?php
do_action( 'save_post', int $post_ID, WP_Post $post, bool $update );

This hook fires when any post in WordPress is saved. It passes a post ID, a post object, and a Boolean value to tell actions whether the post is new or is an update. This can be useful data to have on hand when attaching a custom action to the save_post hook.

What Is an Action?

Actions are PHP functions or class methods. What makes it an action is the act of registering it to an action hook. In the previous section, you learned what action hooks are. However, without actions attached to them, the hooks don't do anything on their own.

This is the point where plugins actually do something. When building plugins, you create custom actions that perform a specific task. Then, you attach that action to an action hook. WordPress provides the add_action() helper function for handling this.

<?php
add_action(
      string $tag, 
      callable $function_to_add, 
      int $priority = 10, 
      int $accepted_args = 1 
);

This function accepts up to four parameters.

  • $tag: Name of the action hook tag, which is the first parameter for do_action().
  • $function_to_add: A PHP callable, such as a function or class method, that executes when the action hook is fired.
  • $priority: In what order the action should be fired. The default is 10, and negative numbers are allowed. Note that other plugins can set higher or lower priorities than your plugin's actions.
  • $accepted_args: The number of parameters to pass to your callback function. By default, only the first parameter will be passed to the function if the action has a parameter.

Action hooks can have multiple actions attached to them. Your plugin, other plugins, themes, and even WordPress can add actions to the same hook. For this reason, it's important not to do anything that would interfere with other actions that may execute after yours.

Now you should try creating your first action and attaching it to a hook. wp_footer is another common action hook executed on the frontend. It's fired just before the closing </body> tag in the HTML output. With the following example code, you'll create a basic plugin that outputs a message that says the site is powered by WordPress:

<?php
/**
 * Plugin Name: Footer Message
 * Plugin URI:  http://example.com/
 * Description: Displays a powered by WordPress message in the footer.
 * Author:      WROX
 * Author URI:  http://wrox.com
 */
 
add_action( 'wp_footer', 'pdev_footer_message', PHP_INT_MAX );
 
function pdev_footer_message() {
       esc_html_e( 'This site is powered by WordPress.', 'pdev' );
}

Take a look at the add_action() call from the example plugin, shown here:

add_action( 'wp_footer', 'pdev_footer_message', PHP_INT_MAX );

The function call has three parameters passed into it. wp_footer is the action hook tag/name, which tells WordPress what action hook this action is attached to. pdev_footer_message is the name of the function that should execute when the action hook is fired. PHP_INT_MAX is the priority of the action. It's set to the maximum integer number possible, which tells WordPress that you want your action to execute after everything else (except actions with the same priority, which will execute in order added).

Action Hook Functions

When building plugins, you will almost exclusively use add_action() or do_action() for working with hooks. However, there are times when you'll need to use one of the other functions WordPress provides for working with action hooks.

remove_action()

remove_action() is the third most useful function when working with action hooks. It allows you to remove an action that has been previously attached to a hook. At times, you may need to remove actions that WordPress adds. You may also need to remove actions from other plugins, themes, or actions that you've added within your own plugin.

The function returns true if the action was successfully removed and false if the action isn't registered or otherwise could not be removed.

<?php
remove_action( string $tag, callable $function_to_remove, int $priority = 10 );

This function accepts three parameters.

  • $tag: The name of the hook that you want to remove the action from
  • $function_to_remove: The name of the callable action to remove from the action hook
  • $priority: The priority of the action to remove

Each of these parameters must match the parameters passed into the corresponding do_action() function call exactly. Otherwise, WordPress will not remove the action from the hook.

Let's take another look at the action from the previous section in this chapter that added a custom message to the footer on the frontend.

<?php
add_action( 'wp_footer', 'pdev_footer_message', PHP_INT_MAX );

Now look at the code required to remove that action.

<?php
remove_action( 'wp_footer', 'pdev_footer_message', PHP_INT_MAX );

As you can see, the code is almost the same. The only difference is between the function names: add_action() and remove_action(). If you remember this basic difference, you can remove actions with ease.

You can remove any action added by WordPress, plugin, or theme from within your own plugins. Most often, you'll be removing actions that WordPress adds to change how it works in some way. Many of WordPress’ default actions are defined in the wp‐includes/default‐filters.php file. By browsing the code in this file, you'll get a good understanding of how WordPress uses its own action hooks out of the box.

remove_all_actions()

In rare cases, you may need to remove all actions for a given action hook or all actions with a specific priority on an action hook. More often than not, this may be something like a testing or debugging type of plugin.

WordPress provides the remove_all_actions() function to handle this rather than running multiple remove_action() calls. This function always returns true when it has finished executing.

<?php
remove_all_actions( string $tag, int|bool $priority = false );

The $tag parameter must be the name of the action hook to remove all actions from. You can set the $priority parameter to a specific number, which will remove only those actions that have that priority. Otherwise, the function will remove all actions, regardless of priority.

Imagine that you wanted to stop all scripts, stylesheets, meta tags, or anything else from being output in the <head> area on the frontend of the site. You could do so by removing all actions from the wp_head hook.

<?php
remove_all_actions( 'wp_head' );

Let's say you wanted to remove all actions from that hook that had the priority of 1. By default, WordPress adds the following action with that priority. However, other plugins or the user's theme may also add extra actions on this hook.

<?php
add_action( 'wp_head', '_wp_render_title_tag', 1 );

To remove this action as well as all other actions with the same priority, you would use the following code:

<?php
remove_all_actions( 'wp_head', 1 );

do_action_ref_array

The do_action_ref_array() function is nearly identical to the do_action() function. They both create an action hook. The difference is with how arguments are passed. Instead of passing multiple values as the second parameter and beyond, the function accepts an array of arguments. This array is passed along as parameters to any actions attached to the hook.

<?php
do_action_ref_array( string $tag, array $args );

Now take a look at one of WordPress’ do_action_ref_array() calls. The following code snippet shows the core pre_get_posts hook, which is fired just before WordPress queries posts from the database on the frontend of the site. It provides a point in the load for plugins to change how posts are loaded.

<?php
do_action_ref_array( 'pre_get_posts', array( &$this ) );

You can see from the code that the hook name is pre_get_posts. The second parameter is an array with a single item. What this code is doing is passing along the instance of the WP_Query class ( $this) for the given posts query. This allows plugin authors to directly modify the query.

Imagine that you wanted to build a simple plugin that randomly ordered the posts on the blog home page instead of the default ordering by post date. You would need to register a custom action on this hook and set the ordering.

<?php
/**
 * Plugin Name: Random Posts
 * Plugin URI:  http://example.com/
 * Description: Randomly orders posts on the home/blog page.
 * Author:      WROX
 * Author URI:  http://wrox.com
 */
 
add_action( 'pre_get_posts', 'pdev_random_posts' );
 
function pdev_random_posts( $query ) {
 
       if ( $query->is_main_query() && $query->is_home() ) {
             $query->set( 'orderby', 'rand' );
       }
}

has_action

Sometimes you need to check whether a hook has any actions or whether a specific action has been added to a hook prior to executing code. The has_action() function is a conditional that returns true if an action is found or false if no action is found. Like remove_action() discussed earlier in this chapter, the check will work only if the action has already been added before your check.

<?php
has_action( string $tag, callable|bool $function_to_check = false );

Like most other action‐related functions, the first parameter is the hook name that you want to check. The second parameter is optional and defaults to false. You can provide the callable if you want to check for a specific function or method attached to the hook.

The return value for has_action() can be either a Boolean value or an integer, depending on the scenario. If the $function_to_check parameter is set to false, the function will return true if the hook has any actions or false if it has none.

However, if $function_to_check is set and the callback has been added to the hook, it will return an integer corresponding to the priority of the action. Otherwise, it will return false. Because an action's priority can be 0, which evaluates to false, it's important to use the identical comparison operator ( ===) rather than the equal comparison operator ( ==) in this scenario.

In the next example, you will display a message based on whether any actions have been registered to the wp_footer action hook.

<?php
if ( has_action( 'wp_footer' ) ) {
       echo '<p>Actions are registered for the footer.</p>';
} else {
       echo '<p>No actions are registered for the footer.</p>';
}

Try out an example where you check for a specific action attached to a hook. The following snippet will check if the wp_print_footer_scripts action is running on wp_footer. It also assigns the result of has_action() to the $priority variable. Remember that the result can be false or an integer representing the action's priority.

<?php
$priority = has_action( 'wp_footer', 'wp_print_footer_scripts' );
 
if ( false !== $priority ) {
       printf(
             'The wp_print_footer_scripts action has a priority of %d',
             absint( $priority )
       );
}

did_action()

did_action() is another conditional function. The name may be confusing at first. You would think that it checks whether a specific action (function) has executed. However, its purpose is to determine whether an action hook has already run.

<?php
did_action( string $tag );

The function accepts a single parameter named $tag, which should be the tag name for the hook. It returns an integer corresponding to the number of times the action hook has fired (remember that action hooks can fire multiple times). Note that it doesn't return a Boolean value if the hook hasn't yet executed. Instead, it returns 0, which evaluates to but is not identical to false.

The first action hook available to plugins is plugins_loaded. Imagine that you wanted to check whether that hook has fired before setting a constant that your plugin is ready.

<?php
if ( did_action( 'plugins_loaded' ) ) {
       define( 'PDEV_READY', true );
}

current_action

The current_action() function returns the name of the action hook that is currently being executed. It is generally useful if you need to use a single action on multiple action hooks but need the context of the hook to alter how something works. It can also be used in any scenario where you might not be sure what the action hook is, such as variable hooks.

For example, assume you have a couple of actions for specific post types on the following core WordPress hook (variable hook names are covered in the “Variable Hooks” section later in this chapter):

<?php
do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );

The previous hook name changes depending on context. It could be save_post_post, save_post_page, or something else entirely. Study the following code that adds a single action to two different action hooks:

<?php
add_action( 'save_post_post', 'pdev_check_hook_name' );
add_action( 'save_post_page', 'pdev_check_hook_name' );
 
function pdev_check_hook_name() {
 
       $action = current_action();
 
       if ( 'save_post_post' === $action ) {
             // Do something.
       } elseif ( 'save_post_page' === $action ) {
             // Do something different.
       }
}

As you can see, current_action() may return different results based on which action hook is currently running. The preceding code uses the return value of the function to set up a conditional statement.

register_activation_hook and register_deactivation_hook

WordPress provides functions for registering actions that will execute when a plugin is activated, deactivated, and even uninstalled. Technically, these functions allow you to create custom hooks and register a callback. These are covered in Chapter 2, “Plugin Framework.”

Commonly Used Action Hooks

Between WordPress, third‐party plugins, and themes, a typical page request could run hundreds of action hooks. Even just the hooks provided by core WordPress is more than can be covered in this chapter alone. However, you'll learn some of the more common hooks you'll use when building plugins in this section.

plugins_loaded

The most useful action hook for plugin developers is plugins_loaded. It is fired immediately after WordPress has loaded all plugins. It's one of the earliest hooks that plugins can attach an action to and is ideal for running any setup code that your plugin might need. It fires before WordPress loads some of its constants and pluggable functions that plugins are allowed to override too.

Many plugin authors use this hook to set constants or properties on their main plugin class. Let's create a basic plugin that uses this hook and calls a setup class. First, you'll need to create a new folder for your plugin named plugin‐bootstrap. Then, add a file named plugin.php as the primary plugin file with the following code:

<?php
/**
 * Plugin Name: Plugin Bootstrap
 * Plugin URI:  http://example.com/
 * Description: An example of bootstrapping a plugin.
 * Author:      WROX
 * Author URI:  http://wrox.com
 */
 
add_action( 'plugins_loaded', 'pdev_plugin_bootstrap' );
 
function pdev_plugin_bootstrap() {
 
       require_once plugin_dir_path( __FILE__ ) . 'Setup.php';
 
       $setup = new PDEVSetup();
 
       $setup->boot();
}

As you can see, the code adds an action to plugins_loaded. The action calls a setup class named Setup and calls its boot() method. You'll also need to create a Setup.php file for the class with the following code:

<?php
namespace PDEV;
 
class Setup {
 
       public $path;
 
       public function boot() {
 
             // Store the plugin folder path.
             $this->path = plugin_dir_path( __FILE__ );
 
             // Run other setup code here.
       }
}

All this code does is store the plugin directory path when the boot() method is called. There are many things you can do here, depending on what your plugin needs to do. You don't even need to use a class. There are dozens or hundreds of ways plugins set themselves up using the plugins_loaded hook. You'll eventually figure out what best works for you.

init

The init hook may be the most‐used hook in WordPress. It is fired after most of WordPress is set up. Unlike plugins_loaded, themes also have access to this hook. WordPress adds a lot of internal functionality to the hook such as post type and taxonomy registration. This is generally the first hook that will make user data available.

Essentially, if you need to run any code when most of the data and functions are available to you from WordPress, this is the earliest hook you should use.

Imagine that you wanted to add excerpt support for pages in WordPress because the page post type doesn't support this feature by default. You could use the following code to add the feature:

<?php
add_action( 'init', 'pdev_page_excerpts' );
 
function pdev_page_excerpts() {
       add_post_type_support( 'page', [ 'excerpt' ] );
}

admin_menu

The admin_menu hook is one of the most commonly used WordPress admin hooks. It's fired early in the page load process and is also the proper hook for registering custom admin pages.

The next example adds a basic top‐level menu page to the WordPress admin. Typically, you would do something more complex, such as create a settings page (for more information on settings pages, see Chapter 3, “Dashboard and Settings”).

<?php
add_action( 'admin_menu', 'pdev_menu_page' );
 
function pdev_menu_page() {
 
       add_menu_page(
             'PDEV Page',
             'PDEV Page',
             'manage_options',
             'pdev-page',
             'pdev_menu_page_template'
       );
}
 
function pdev_menu_page_template() { ?>
 
       <div class="wrap">
             <h1 class="wp-heading-inline">PDEV Example Page</h1>
 
             <p>This is an example admin screen.</p>
       </div>
<?php }

save_post

The save_post hook executes after a post is saved for the first time or updated. Plugins often use this to store data related to the post, such as post metadata. It's not tied to a specific page, such as the edit post admin screen or the frontend. Posts are saved in a variety of ways at various times, and the save_post hook runs every time.

Consider a scenario where you wanted to save some metadata for a post only if it's a new post and not an update. You also don't want to save this metadata for post revisions, which are stored as a separate post. The following code will store the metadata when the save_post hook is fired. You'll learn more about metadata in Chapter 8, “Content.”

<?php
add_action( 'save_post', 'pdev_save_post', 10, 3 );
 
function pdev_save_post( $post_id, $post, $update ) {
 
       if ( $update || wp_is_post_revision( $post_id ) ) {
             return;
       }
 
       add_post_meta(
             $post_id,
             'pdev_meta_key',
             'This is an example meta value.',
             true
       );
}

wp_head

On the frontend of a WordPress site, themes are required to call the wp_head() function, which fires the wp_head hook. This allows plugins to inject HTML between the opening <head> tag and its closing </head>.

Typically, plugins use this hook to add SEO features such as meta tags. The following example adds a meta description tag based on the page currently being viewed by the website visitor:

<?php
add_action( 'wp_head', 'pdev_meta_description' );
 
function pdev_meta_description() {
 
       $description = '';
 
       // Get site description for front page.
       if ( is_front_page() ) {
             
             $description = get_bloginfo( 'description', true );
 
       // Get post excerpt for singular views.
       } elseif ( is_singular() ) {
 
             $post = get_queried_object();
 
             if ( $post->post_excerpt ) {
                    $description = $post->post_excerpt;
             }
       }
 
       if ( $description ) {
             printf(
                    '<meta name="description" content="%s"/>',
                    esc_attr( wp_strip_all_tags( $description ) )
             );
       }
}

There are dozens of SEO plugins out there, but don't let that discourage you from using the previous code to kick‐start your own competing product and gain a foothold on the market.

FILTERS

Filter hooks are the second type of hook in WordPress. They are similar to action hooks in some ways. However, they are much different in their most essential feature, which is to allow you to manipulate the output of code. Filter hooks work by always passing data through the hook, and any filters on the hook can modify the data in any way.

To understand how filter hooks work, you must first learn how the apply_filters() WordPress function works. It's the foundation of the filter hook system.

<?php 
apply_filters( string $tag, mixed $value );

The function accepts two or more parameters.

  • $tag: The unique name for the filter hook.
  • $value: The value that gets passed to any filters added to the hook for manipulation.
  • $args: Like the do_action() function for action hooks, the function accepts any number of additional arguments to pass to filters attached to the hook.

The function always returns the $value variable after it has been filtered. It is also imperative that any filters added to the hook also return a value.

The following is an example of a filter hook in core, which should give you a feel for what they look like.

<?php
$template = apply_filters( 'template_include', $template );

In this example, template_include is the name of the filter hook. $template is a string representing a file path for the template (PHP file) to load on the frontend of the site to display the current page. A plugin could filter this file path to let WordPress know it should load the template from a different location.

What Is a Filter?

Like actions covered earlier in this chapter, filters are nothing more than a function or other PHP callable. What makes the function a filter is the act of registering it for a filter hook. If a filter hook doesn't have any filters on it, nothing happens. The data is returned unchanged. Filter hooks exist so that plugin developers can create custom functions that filter some type of data in some way. This can be a simple text string, an array, or any other valid data type in PHP.

When the apply_filters() function is called, any filters attached to it are executed in the order of their priority. You use the add_filter() function to register a filter.

<?php
add_filter(
      string $tag,
      callable $function_to_add,
      int $priority = 10,
      int $accepted_args = 1
);

This function accepts up to four parameters.

  • $tag: The name of the filter hook tag, which is the first parameter for apply_filters().
  • $function_to_add: A PHP callable, such as a function or class method, that executes when the filter hook is applied.
  • $priority: The order in which the filter should be applied to the hook. The default is 10, and negative numbers are allowed.
  • $accepted_args: The number of parameters to pass to your callback function. By default, the $value parameter from apply_filters() will always be passed as the first parameter.

You can add any number of filters to the same filter hook. WordPress itself or third‐party plugins may also have filters applied to the same hook. It's important to understand this because each filter can manipulate the data in any way it wants. When adding a filter, you must always return the same type of data that is expected for a hook. Otherwise, you risk breaking other plugins or even WordPress, which creates a poor experience for plugin users.

Now look at an example filter hook from WordPress. It is the body_class hook that applies to the HTML classes in the <body> element on the frontend of the site.

<?php
$classes = apply_filters( 'body_class', $classes, $class );

There are three things you should note about this hook.

  • body_class: The name of the hook.
  • $classes: An array of HTML classes. Filters must return a manipulated version of this or an empty array.
  • $class: A user‐submitted class passed as a second parameter if needed for filters.

Now you're going to write a basic function that adds class to the $classes array and returns it.

<?php
add_filter( 'body_class', 'pdev_body_class' );
 
function pdev_body_class( $classes ) {
 
       $classes[] = 'pdev-example';
 
       return $classes;
}

The preceding filter is simple. It adds the pdev‐example class to the array. WordPress will later format that array to a string and output it as a class attribute to the <body> element. You could run any code within the filter to determine whether you want to add a class. For example, you may want to add a class only on certain pages on the frontend related to your plugin.

Filter Hook Functions

You've learned how apply_filters() and add_filter() work, which are the basics of using filters. For the most part, you will almost always use these two functions. However, there are cases where you will need to utilize the other filter‐related functions provided by WordPress.

remove_filter

One of the more important functions related to filters is the remove_filter() function. It allows plugins to remove filters that are registered to a filter hook but only if they have been registered earlier in the page load than the remove_filter() function call.

<?php
remove_filter( string $tag, callable $function_to_remove, int $priority = 10 );

The function accepts three parameters.

  • $tag: The name of the filter hook to remove the filter from
  • $function_to_remove: The callback function or method to remove from the filter hook
  • $priority: The exact priority of the filter added to the hook

The function will return true when the filter is successfully removed and false otherwise. Note that the $priority parameter must match the $priority of the original add_filter() call for the filter. Otherwise, it will not be removed.

Now take a look at one of WordPress’ default filters, which is defined in wp‐includes/default‐filters.php. This particular filter converts smiley characters such as :), :(, and ;) into images for post content on the frontend.

<?php
add_filter( 'the_content', 'convert_smilies', 20 );

Perhaps you want to disable these smiley images because you don't think they look professional or for some other reason. By simply looking at the add_filter() call, you have all the information you need to remove it: the hook name, the function name, and the priority. Now try the following code to disable smiley‐to‐image conversion:

<?php
remove_filter( 'the_content', 'convert_smilies', 20 );

As you can see, the add_filter() and remove_filter() calls are nearly identical. You shouldn't have any trouble removing filters after trying it a couple of times.

remove_all_filters

At times, you may need to remove all filters from a filter hook or just all filters with a specific priority. The remove_all_filters() function allows you to do this with a single line of code. Similar to remove_filter(), it can remove only the filters that have already been registered at the time the code runs.

<?php
remove_all_filters( string $tag, int|bool $priority = false );

The function accepts a $tag parameter, which must be the tag name for the hook to remove filters from. The second parameter, $priority, is optional. If set to false (the default), it will remove all filters from the hook. If set to a specific integer, it will remove only those filters that have a matching priority.

After the function has completed running, it will always return true. Note that it doesn't return false if no filters were removed.

Now take a look at this list of the filters that core WordPress runs over its the_content filter hook:

<?php
add_filter( 'the_content', 'do_blocks', 9 );
add_filter( 'the_content', 'wptexturize' );
add_filter( 'the_content', 'convert_smilies', 20 );
add_filter( 'the_content', 'wpautop' );
add_filter( 'the_content', 'shortcode_unautop' );
add_filter( 'the_content', 'prepend_attachment' );
add_filter( 'the_content', 'wp_make_content_images_responsive' );

Imagine that you wanted to remove all those filters and any others registered for the filter hook. You'd use the following code:

<?php
remove_all_filters( 'the_content' );

Now suppose you wanted to remove only those filters with a priority of 9 (the first filter in the earlier code block has this priority). You would need to set the second parameter, as shown here:

<?php
remove_all_filters( 'the_content', 9 );

apply_filters_ref_array

The apply_filters_ref_array() function is similar to the primary apply_filters() function. It creates a filter hook. However, the major difference is that it accepts an array of arguments to pass to filters.

<?php
apply_filters_ref_array( string $tag, array $args );

The $tag parameter is the name of the hook. The $args parameter is the array of arguments, which will be split up in order as parameters to pass to any filter callback functions.

Take a look at the following example filter hook from core WordPress’ WP_Query class, which returns an array of posts that should be displayed for a given page.

<?php
$this->posts = apply_filters_ref_array( 
      'the_posts', 
      array( $this->posts, &$this ) 
);

In reality, this would not be any different from the equivalent code using apply_filters().

<?php
$this->posts = apply_filters( 'the_posts', $this->posts, &$this );

Adding custom filters to this type of filter hook is no different. Imagine that you wanted to remove the first post from the posts list. Perhaps you're showing it in a different way elsewhere on the page. You'd use the following code to remove the first post from the array:

<?php
add_filter( 'the_posts', 'pdev_the_posts' );
 
function pdev_the_posts( $posts ) {
 
       if ( isset( $posts[0] ) ) {
             unset( $posts[0] );
       }
 
       return $posts;
}

has_filter

has_filter() is a conditional function that allows plugin authors to check whether any filters have been registered or whether a specific filter has been registered for a filter hook. Similar to remove_filter(), the function will recognize a filter as registered only if the call to has_filter() comes after add_filter() in the page load process.

<?php
has_filter( string $tag, callable|bool $function_to_check = false );

The first parameter of $tag should be the name of the hook to check against. The second parameter of $function_to_check is optional. If omitted, the function will check whether any filters are registered for the hook. If a specific function is passed, the function will check whether that function is registered as a filter.

The function will return different values based on whether $function_to_check is set to false (the default) or a function. If checking against a specific function, the $priority parameter passed into add_filter() will be returned if it is registered. Note that priorities can have a value of 0, so it's important to perform identical evaluation ( ===) instead of equals evaluation ( ==). If the filter is not registered to the hook, the function will return false.

If no $function_to_check parameter is set, the function will return either true or false, depending on whether any filters are registered for the hook.

Suppose you wanted to check whether there were any filters on the post content and display a message if any were found. You'd use the following code:

<?php
if ( has_filter( 'the_content' ) ) {
       echo 'There are filters on the post content.';
}

You could also get the priority of a specific filter on the post content, such as determining when the editor blocks are parsed, using the next code snippet.

<?php
$priority = has_filter( 'the_content', 'do_blocks' );
 
// Returns:
// 9

current_filter

The current_filter() function returns the name of the filter hook that is currently being executed. This function is particularly useful if you use a single filter function on multiple filter hooks. There are cases where you might want the function to execute differently depending on the current filter hook.

Imagine that you had a client who wanted to replace specific “bad” words from post titles and post content. However, the bad words are different depending on context. You could write two different filter functions, or you could combine them into one and use current_filter() to determine which filter hook (the post title or post content hook) is running.

With the following code, you could write a plugin that replaces any of the bad words with *** so that they don't appear on the site:

<?php
/**
 * Plugin Name: Remove Bad Words
 * Plugin URI:  http://example.com/
 * Description: Removes bad words from the post title and content.
 * Author:      WROX
 * Author URI:  http://wrox.com
 */
 
add_filter( 'the_title',   'pdev_remove_bad_words' );
add_filter( 'the_content', 'pdev_remove_bad_words' );
 
function pdev_remove_bad_words( $text ) {
 
       $words = [];
 
       if ( 'the_title' === current_filter() ) {
             $words = [
                    'bad_word_a',
                    'bad_word_b'
             ];
       } elseif ( 'the_content' === current_filter() ) {
             $words = [
                    'bad_word_c',
                    'bad_word_d'
             ];
       }
 
       if ( $words ) {
             $text = str_replace( $words, '***', $text );
       }
 
       return $text;
}

Quick Return Functions

There are times when you may need to return values like an empty array, empty string, or similar simple types of simple data with filters. WordPress provides several quick return functions so that you don't have to create a function to handle it.

For example, the following is an example of returning an empty array with a filter to remove all user contact methods:

<?php
add_filter( 'user_contactmethods', 'pdev_return_empty_array' );
 
function pdev_return_empty_array() {
       return [];
}

There's no need to write a custom function in that scenario. Instead, you could use the core WordPress __return_empty_array() function and handle the filter with a single line of code.

<?php
add_filter( 'user_contactmethods', '__return_empty_array' );

WordPress provides several quick return functions for various scenarios.

  • __return_true(): Returns a Boolean true value
  • __return_false(): Returns a Boolean false value
  • __return_zero(): Returns the integer 0
  • __return_empty_array(): Returns an empty array
  • __return_null(): Returns a null value
  • __return_empty_string(): Returns an empty string

As you've probably noticed, each of these function names begin with a double underscore. This is a quick way to recognize quick return functions in WordPress. If there's not a common function available for returning a specific value, you can also create your own for use in your plugins. Core WordPress simply covers some of the most common use cases.

Commonly Used Filter Hooks

WordPress has hundreds of filter hooks that may be fired on any given page. There's no way this book could cover each of them. New filter hooks are added over time too. This section covers a few of the more common filter hooks to provide a look at what is possible with custom filters.

the_content

There's one hook that is used more than any other by plugin authors, which is the_content. It's one of the most important hooks on most frontend pages of the site because it allows a plugin to filter the output of the post content. Plugins typically do one of two things on this hook: add extra formatting to the existing content or append extra HTML to the end of the content.

You will now build a plugin that adds a subscription form to the end of single post content on the frontend.

<?php
/**
 * Plugin Name: Content Subscription Form
 * Plugin URI:  http://example.com/
 * Description: Displays a subscription form at the end of the post content.
 * Author:      WROX
 * Author URI:  http://wrox.com
 */
 
add_filter( 'the_content', 'pdev_content_subscription_form', PHP_INT_MAX );
 
function pdev_content_subscription_form( $content ) {
 
       if ( is_singular( 'post' ) && in_the_loop() ) {
 
             $content .= '<div class="pdev-subscription">
                    <p>Thank you for reading. 
                    Please subscribe to my email list for updates.</p>
                    <form method="post">
                          <p>
                                 <label>
                                        Email:
                                        <input type="email" value=""/>
                                 </label>
                          </p>
                          <p>
                                 <input type="submit" value="Submit"/>
                          </p>
                    </form>
             </div>';
       }
 
       return $content;
}

Of course, you'd need to connect that form to an email or feed system for it to actually work. The preceding code is simply to show how to append such a form to the end of the content.

template_include

The template_include hook is a hook where plugins intersect with the theme system by overwriting or changing the template included (templates are just PHP files). At times, it's not a perfect system because themes are in ultimate control of what templates exist and conflicts arise. However, plugins filtering the included template can minimize any damage.

This particular filter hook is a catchall hook for many other, more‐specific filter hooks related to templates. It is fired after the theme template file has been chosen for the current page view.

Suppose you had a custom post type named movie in which you wanted to use a template defined by the plugin if the template doesn't exist in the theme (custom post types are covered in Chapter 8, “Content”). You want to check whether the theme has a pdev‐movie‐archive.php template for the post type archive and a pdev‐single‐movie.php for the single movie post. If not found, you want to use the templates stored in your plugins custom /templates folder.

<?php
add_filter( 'template_include', 'pdev_template_include' );
 
function pdev_template_include( $template ) {
 
       if ( is_post_type_archive( 'movie' ) ) {
 
             $template = locate_template( 'pdev-movie-archive.php' );
 
             if ( ! $locate ) {
                    $template = require_once plugin_dir_path( __FILE__ )
                                . 'templates/pdev-movie-archive.php';
             }
 
       } elseif ( is_singular( 'movie' ) ) {
 
             $template = locate_template( 'pdev-single-movie.php' );
 
             if ( ! $locate ) {
                    $template = require_once plugin_dir_path( __FILE__ )
                                . 'templates/pdev-single-movie.php';
             }
       }
 
       return $template;
}

You could also split these filters into two separate functions and register them for the archive_template and single_template filter hooks. However, those hooks fire earlier in the page load process.

USING HOOKS FROM WITHIN A CLASS

Throughout most of this chapter, you've seen code examples of using action and filters with PHP functions. This makes it simpler to show how actions and filters work. However, in most professional plugin projects, you'll want to use PHP classes to build your plugins. When adding a class method as an action or filter, the format of the add_action() and add_filter() calls will be slightly different.

You will primarily be working with object instances in object‐oriented programming. Therefore, you need a reference to the object ( $this from within the class). When working with objects, the format of the function calls will look like the following:

<?php
add_action( $tag, [ $object, $method ] );
add_filter( $tag, [ $object, $method ] );

The second parameter becomes an array with the object reference and the method name of the class.

Now you will rebuild the action from earlier in this chapter that created random posts on the blog page. Instead of using a basic function, you will do it from within a class.

<?php
namespace PDEV;
 
class RandomPosts {
 
       public function boot() {
             add_action( 'pre_get_posts', [ $this, 'randomize' ] );
       }
 
       public function randomize( $query ) {
 
             if ( $query->is_main_query() && $query->is_home() ) {
                    $query->set( 'orderby', 'rand' );
             }
       }
}
 
( new RandomPosts() )->boot();

At times, you may also need to run an action or filter from within a static class. Because you don't have an object reference, you need to reference the class name instead. Take a look at the same random posts code from within a static class:

<?php
namespace PDEV;
 
class RandomPosts {
 
       public static function randomize( $query ) {
 
             if ( $query->is_main_query() && $query->is_home() ) {
                    $query->set( 'orderby', 'rand' );
             }
       }
}
 
add_action( 'pre_get_posts', [ 'PDEVRandomPosts', 'randomize' ] );

Take note that the code references the fully qualified class name. WordPress won't recognize your namespace in add_action() and add_filter() calls, so it's important to use the full class name. You can also use __NAMESPACE__ to get the current namespace.

USING HOOKS WITH ANONYMOUS FUNCTIONS

There are times when you may want to use an anonymous function (closure) with an action or filter. Typically, these are quick functions where it doesn't make sense to build out a full, named function. They can also be useful where you want to run an action/filter that you don't want other plugins to remove.

For example, take a look at the following code that runs on the plugins_loaded hook for setting up a plugin. Small plugins may not need a large setup class and simply want to jump‐start some quick setup code.

<?php
add_action( 'plugins_loaded', function() {
       // Run plugin setup.
} );

As you can see, the second parameter registering an anonymous action or filter is simply the anonymous function itself. There's no reference to a function name, class name, or object.

CREATING CUSTOM HOOKS

One of the great things about hooks in WordPress is that plugin developers are not limited to the hooks that WordPress creates. Plugins can contain custom hooks using the standard functions covered earlier in this chapter.

  • do_action()
  • do_action_ref_array()
  • apply_filters()
  • apply_filters_ref_array()

You can use the first two functions for creating custom action hooks and the last two for creating custom filter hooks just like core WordPress.

Benefits of Creating Custom Hooks

Custom hooks make your plugin more flexible by allowing it to be extended by other plugin and theme developers. It also gives you the ability to execute code or filter data within the plugin itself.

One of the biggest advantages of using the hook system is to keep users or developers from editing your plugin's code, which makes it incompatible with future updates. With hooks, others can create custom actions and filters to modify how your plugin works without losing their modifications. It's always important to think about what data could be modified via a filter or what event might make for a good action filter when writing your plugin's code.

Custom Action Hook Example

In the following example, you create a plugin setup/load function, which is hooked to plugins_loaded. This code adds an action hook before and after your plugin's setup runs. Doing this provides an easy way for other plugins to run code at those specific points.

<?php
add_action( 'plugins_loaded', 'pdev_load' );
 
function pdev_load() {
 
       do_action( 'pdev_load_before' );
 
       // Run setup code.
 
       do_action( 'pdev_load_after' );
}

Imagine you had a large plugin with those action hooks where other plugin developers created “add‐on” plugins that extended your plugin. The pdev_load_after action hook would be a good point for the add‐ons to set themselves up. It also makes it easy for them to run code only if your plugin is active. The following code snippet shows how such a plugin might set itself up on that hook:

<?php
add_action( 'pdev_load_after', 'pdev_addon_load' );
 
function pdev_addon_load() {
       // Add-on plugin sets itself up.
}

Custom Filter Hook Example

Suppose you have a form in your plugin that saves some text that will eventually get output on the frontend. By default, this text has no formatting. You might want to allow others to filter the final output of the text.

<?php
function pdev_example_text() {
 
       $text = apply_filters(
             'pdev_example_text',
             'This is some example text the user has saved.',
       );
 
       echo $text;
}

Another plugin could handle a feature such as adding automatic paragraph tags using the WordPress wpautop() function by adding it as a filter on the pdev_example_text hook.

<?php
add_filter( 'pdev_example_text', 'wpautop' );

A developer could also write a custom filter function just like with any hook and do any sort of formatting. There are no real limits to what can be done.

FINDING HOOKS

It would be nearly impossible to list every hook available in WordPress in this book. You learned about some of the most common action and filter hooks earlier in this chapter, but those sections cover a small sampling of what hooks WordPress offers to plugin developers.

New hooks are almost always added with new versions of WordPress. As a developer, you'll want to keep track of changes in the core code with each update, which will help you stay on top of new hooks as they become available.

Searching for Hooks in the Core Code

The best thing any developer at any level can do is familiarizing themselves with the code of the software they're building on. Books such as Professional WordPress Plugin Development can help you learn the “how” of building plugins, but nothing beats simply reading the code for learning how to program for that system.

The easiest way to start finding hooks is to open WordPress in your preferred code editor and use its search or find function. Using this feature of your editor, you merely need to search for one of the following strings.

  • do_action
  • do_action_ref_array
  • apply_filters
  • apply_filters_ref_array

Once you get a hit with your editor's search, you might feel overwhelmed by the number of results that turn up. Take your time and read the code. You have the knowledge of how hooks work. At this point, it is a matter of figuring out what the specific hook does.

Variable Hooks

When searching for hooks in the core WordPress code, you will eventually come across what is called variable hooks. Throughout this chapter, you have only seen hooks with a static string of text for their name, which is the most common case. However, some hooks use PHP variables in their names, which can change the hook name depending on the variable's current value.

The save_post_{$post‐>post_type} hook is a good example of such a hook. The $post‐>post_type variable changes depending on what type of post is currently being saved.

<?php
do_action( "save_post_{$post->post_type}", $post->ID, $post, true );

Because the variable changes depending on the post type, the real hook name could be save_post_post (“post” post type), save_post_page (“page” post type), save_post_movie (a custom “movie” post type), or any other post type name.

WordPress has several variable hooks in the core code. These hook names are useful because they provide context to plugin developers, which allows them to execute only under specific circumstances. Keep this in mind when searching for the perfect hook to use for your plugin.

Hook Reference Lists

Searching for hooks in the core code is a great way to learn, but sometimes you may find it easier to access the publicly available references in the WordPress developer documentation. These references can save you time and may also have useful notes.

Chapter 16, “The Developer Toolbox,” has additional reference materials for plugin authors to use when building plugins.

SUMMARY

The Plugin API is the most important aspect of building WordPress plugins. With nearly every plugin you build, your plugin will hook its functions into one or more WordPress action or filter hooks. You'll make extensive use of everything you have learned in this chapter in your journey of building plugins. In many ways, the hooks system is what sets WordPress apart from many other platforms. It provided a leg up over the competition early on because it was built to be extended, and hooks are nothing more than a way for plugins to extend WordPress.

Armed with a full understanding of how hooks work, you can now start building real plugins.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.22.107