Blog posts under the hooks tag https://webdevstudios.com/tags/hooks/ WordPress Design and Development Agency Mon, 15 Apr 2024 16:03:24 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.2 https://webdevstudios.com/wp-content/uploads/2022/07/cropped-wds-icon.white-on-dark-60x60.png Blog posts under the hooks tag https://webdevstudios.com/tags/hooks/ 32 32 58379230 Debugging WordPress Core: Actions and Filters https://webdevstudios.com/2019/01/22/debugging-wordpress-core-actions-filters/ https://webdevstudios.com/2019/01/22/debugging-wordpress-core-actions-filters/#comments Tue, 22 Jan 2019 17:00:39 +0000 https://webdevstudios.com/?p=19386 Most WordPress developers are familiar with the concept of actions and filters. At the very heart of WordPress, these hooks allow developers to extend the functionality of WordPress in numerous ways. Whether you want to run a process when a post is saved, add a new section to the Edit User page, or modify the Read More Debugging WordPress Core: Actions and Filters

The post Debugging WordPress Core: Actions and Filters appeared first on WebDevStudios.

]]>
Most WordPress developers are familiar with the concept of actions and filters. At the very heart of WordPress, these hooks allow developers to extend the functionality of WordPress in numerous ways. Whether you want to run a process when a post is saved, add a new section to the Edit User page, or modify the SQL used when querying the database, WordPress has hooks for (almost) everything.

One thing I’ve noticed a lot, as a frequent user of the WordPress StackExchange, is that many developers don’t know where to start when trying to figure out which actions or hooks might be available to them. In this blog post, I want to help walk through the process of tracking down various hooks with examples of when you might want to use them and how to implement them.

Actions and Filters: What’s the Difference?

This is a very basic mistake I see often when helping people figure out their WordPress issues. Say for example someone wants to modify the post before editing, but they can’t figure out why their code isn’t doing anything. Well, let’s take a look at a basic example that makes a post’s content all-uppercase:

add_action( 'content_edit_pre', function( $post_content ) {
	return strtoupper( $post_content );
} );

Pretty straightforward, right?

GIF animation of a panicked man in front of a blazing fire.

On the surface, this looks like a proper filter but it will never work. The reason is that WordPress makes a distinction between actions and filters. Actions are assigned via add_action, while filters are assigned via add_filter. The corresponding methods to call these are do_action and apply_filters, respectively. Under the hood, there’s actually not much difference. In fact, add_action calls add_filter. Here’s the full source code from WordPress:

function add_action($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
    return add_filter($tag, $function_to_add, $priority, $accepted_args);
}

Kind of crazy, right?

It’s because add_filter and add_action do roughly the same thing at the core level, with one minor exception: the returned value of apply_filters can be used to modify existing data structures, while do_action returns literally nothing (void in PHP).

So, our above example will never return any value to modify the content, as do_action simply doesn’t do that. While these differences may make you want to ask, “Why even have different methods?” the distinction is very important.

Actions are for when you want something to happen as a result of something else happening, while filters are used to modify data at run time. Our above example will work with the exact same code, with one minor modification:

add_filter( 'content_edit_pre', function( $post_content ) {
	return strtoupper( $post_content );
} );

[Edit: thanks @Anton Serednii for the correction!]
In WordPress-speak, we use the term “hook” to refer to actions and filters interchangeably, as they are roughly the same thing. When talking about a specific hook, we use either action or term. (Example: “You want to use the admin_enqueue_scripts action to add scripts to WP Admin,” or, “You can use the body_class filter to add additional CSS classes to a page’s <body> tag.”)

Finding the Right Hook

WordPress has a lot of hooks to use. I mean, a lot. A rough count of the WordPress codebase puts the number of hooks called at around 2,744. That’s a lot of hooks!

A GIF animation of Dustin Hoffman as Captain Hook.

 

So, how do you find the right one? Well, you can refer to the action and filter references linked above (and also check out the Plugin API landing page), but those references cover everything and, as we just discussed. That’s a lot of things.

Furthermore, some of the hooks are still undocumented to this day in the Codex. In some cases, the best way is to identify the method that you want to hook into and then check it out in the source code. Additionally, you might learn some interesting things about the hooks once you dive into where they are called from.

Example 1: The save_post Action

The save_post action is triggered by wp_insert_post and wp_publish_post. Both methods are defined in wp-includes/post.php. If we dig into the source code, we’ll first find this definition of save_post:

/**
 * Fires once a post has been saved.
 *
 * @since 1.5.0
 *
 * @param int $post_ID Post ID.
 * @param WP_Post $post Post object.
 * @param bool $update Whether this is an existing post being updated or not.
 */
do_action( 'save_post', $post_ID, $post, $update );

What’s interesting here is that directly above it we can also see this:

/**
 * Fires once a post has been saved.
 *
 * The dynamic portion of the hook name, `$post->post_type`, refers to
 * the post type slug.
 *
 * @since 3.7.0
 *
 * @param int $post_ID Post ID.
 * @param WP_Post $post Post object.
 * @param bool $update Whether this is an existing post being updated or not.
 */
do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );

That’s neat! We can actually target save_post for our own specific post type. This saves a few lines of doing a check like this:

if ( 'my_custom_post_type' !== $post->post_type ) {
    return;
}

Another important note when it comes to hooks is not to hook something that might bite you later. For example, save_post is a great action to use when you want to do something after a post saves. For instance, you might want to record a new post entry for your publish_log custom post type that tracks when posts are published. Let’s take a look at an example:

function maybe_create_publish_log( $post_id, $post ) {
    if ( 'publish' !== $post->post_status ) {
        return;
    }

    wp_insert_post( [
        'post_type' => 'publish_log',
        'post_author' => $post->post_author,
        'post_status' => 'publish',
        'post_content' => "New post {$post->post_title} published on {$post->post_date}.",
        // etc...
    ] );
}

add_action( 'save_post', 'maybe_create_publish_log', 10, 2 );

At first glance, this seems fine. But remember earlier when we learned that save_post is called by wp_insert_post? With this code, you might find yourself creating tons of  publish_log posts because your hook to save_post is being called over and over by wp_insert_post. So, how do you get around this?

The answer is the aptly named remove_action (if you guessed that its sister function is remove_filter and that remove_action simply calls remove_filter, you get a cookie). Let’s take a look at our code now:

function maybe_create_publish_log( $post_id, $post ) {
    if ( 'publish' !== $post->post_status ) {
        return;
    }

    remove_action( 'save_post', 'maybe_create_publish_log' );
    wp_insert_post( [
       'post_type' => 'publish_log',
       'post_author' => $post->post_author,
       'post_status' => 'publish',
       'post_content' => "New post {$post->post_title} published on {$post->post_date}.",
        // etc...
    ] );
    add_action( 'save_post', 'maybe_create_publish_log', 10, 2 );
}

add_action( 'save_post', 'maybe_create_publish_log', 10, 2 );

This is one of the benefits to diving into the core files: if you look up where your action is called from, you will know if you’re about to get into a publishing loop, or avoid other possible “gotchas” when developing your plugins and themes.

Example 2: Modifying the Edit User Screen

Recently at WebDevStudios, we’ve had a couple of clients that needed specific sections added to the user profile page. You may be looking to integrate a service, such as Medium, to your user accounts, or you may want to give administrators the ability to modify fields specific to a plugin. But where do you start?

If you look in the action reference, well, you might be looking for a long time. You’d eventually find what you’re looking for, but there is an easier way.

First, on a WordPress install, you’ll notice you’re at this page when editing a User: /wp-admin/user-edit.php?user_id=3. If you’re editing your own user, you’ll find that you’re at /wp-admin/profile.phpYou might be thinking, “Oh geez, I have to dig into two files to find the action I want?!”

But fear not, because upon opening profile.php you’ll see it’s actually rather simple:

<?php
/**
 * User Profile Administration Screen.
 *
 * @package WordPress
 * @subpackage Administration
 */

/**
 * This is a profile page.
 *
 * @since 2.5.0
 * @var bool
 */
define('IS_PROFILE_PAGE', true);

/** Load User Editing Page */
require_once( dirname( __FILE__ ) . '/user-edit.php' );

Well, that saves us some time. Now, if you dig into user-edit.php, you’re going to be looking for calls to do_action. Let’s do a quick check with ag – the silver searcher:

  ag do_action wp-admin/user-edit.php

Which gives us the following output:

132: do_action( 'personal_options_update', $user_id );
141: do_action( 'edit_user_profile_update', $user_id );
230: do_action( 'user_edit_form_tag' );
285: do_action( 'admin_color_scheme_picker', $user_id );
347:do_action( 'personal_options', $profileuser );
362: do_action( 'profile_personal_options', $profileuser );
658: do_action( 'show_user_profile', $profileuser );
667: do_action( 'edit_user_profile', $profileuser );

Now, let’s dig into the actual file itself. Odds are we want to be looking at edit_user_profile specifically:

if ( IS_PROFILE_PAGE ) {
	/**
	 * Fires after the 'About Yourself' settings table on the 'Your Profile' editing screen.
	 *
	 * The action only fires if the current user is editing their own profile.
	 *
	 * @since 2.0.0
	 *
	 * @param WP_User $profileuser The current WP_User object.
	 */
	do_action( 'show_user_profile', $profileuser );
} else {
	/**
	 * Fires after the 'About the User' settings table on the 'Edit User' screen.
	 *
	 * @since 2.0.0
	 *
	 * @param WP_User $profileuser The current WP_User object.
	 */
	do_action( 'edit_user_profile', $profileuser );
}

You’ll notice that I copied out the surrounding if conditional. As well, you might notice something familiar on the first line: the if ( IS_PROFILE_PAGE ) check lets you hook in just on your own user page, or only on other users’ pages, or both by a combination of both actions.

With these actions, we can render a custom set of fields to modify the user edit page and bring custom functionality right into the core of WordPress. While the Codex’s action list is quite extensive and sometimes difficult to navigate, it is a wonderful resource for finding additional information on most actions and filters once you know what to look for. Have a peek at the page for edit_user_profile, for example.

Example 3: Modifying Query SQL with Filters

This is a slightly more advanced example, but an important one. WordPress does a lot of things under the hood to construct the MySQL queries that ultimately find your posts, tags, etc. Let’s start with an example WP Query from get_posts:

$args = [
    'post_type'      => 'post',
    'posts_per_page' => 5,
    'orderby'        => 'post_date',
    'order'          => 'ASC',
    's'              => 'Test',
];

$posts = get_posts( $args );

The resulting SQL looks something like this:

SELECT
    wp_posts.ID
FROM
    wp_posts
WHERE 1=1
AND (((wp_posts.post_title LIKE '%Test%') OR (wp_posts.post_excerpt LIKE '%Test%') OR (wp_posts.post_content LIKE '%Test%')))
AND (wp_posts.post_password = '')
AND wp_posts.post_type = 'post'
AND ((wp_posts.post_status = 'publish'))
ORDER BY wp_posts.post_date ASC
LIMIT 0, 5

This query, although simple, is filtered several times before it is passed to the database for record retrieval. A quick aside—the “%” signs may be encoded as long hashes when you view the query, as a placeholder to help with how $wpdb parses placeholders.

Next, we’ll step through some of the more useful filters used. The following code can be found in wp-includes/class-wp-query.php. 

posts_where

This is one of the earliest filters on the query, and filters the WHERE clause of the query. For the above example, this filter’s parameters are the WHERE clause as the first parameter, and a reference to the query object as the second. The WHERE clause looks like this:

    [0] =>  AND (((wp_posts.post_title LIKE '{67feda5925d0533a58f19e45c96ff1c761ddaa2263949b4ae0023a90b3f25b1f}Test{67feda5925d0533a58f19e45c96ff1c761ddaa2263949b4ae0023a90b3f25b1f}') OR (wp_posts.post_excerpt LIKE '{67feda5925d0533a58f19e45c96ff1c761ddaa2263949b4ae0023a90b3f25b1f}Test{67feda5925d0533a58f19e45c96ff1c761ddaa2263949b4ae0023a90b3f25b1f}') OR (wp_posts.post_content LIKE '{67feda5925d0533a58f19e45c96ff1c761ddaa2263949b4ae0023a90b3f25b1f}Test{67feda5925d0533a58f19e45c96ff1c761ddaa2263949b4ae0023a90b3f25b1f}')))  AND (wp_posts.post_password = '') 

Two notes about the above: first, you can see the expanded version of the placeholder for the “%” symbol. Second, the query clause starts with an AND. You may have noticed in the original query that this clause starts with WHERE 1=1. This is a simple trick to allow adding more conditions to the WHERE clause. If we wanted to add an extra field to check against when someone searches, say from another table, we could do something like this:

add_filter( 'posts_where', function( $where, $query ) {
    if ( 'post' !== $query->get( 'post_type' ) {
        return $where;
    }

    if ( ! $query->get( 's' ) ) {
        return $where;
    }

    $search = $query->get( 's' );
    $where .= " AND mycustomtable.ad_keywords LIKE '%{$search}%'";
    return $where;
}, 10, 2 );

This would allow us to search across our custom table for rows with a matching search in the ad_keywords column. If you’re familiar with SQL, you’re probably wondering how we’re querying the mycustomtable table without a JOIN. Well, right below the call to filter posts_where is our next guest…

posts_join

The posts_join filter allows us to write JOIN clauses to other tables we might need in our query. Currently, our query doesn’t join to any other tables since we aren’t doing a taxonomy query or meta query. However, if we want to JOIN our custom table, it’s pretty straight forward.

add_filter( 'posts_join', function( $joins, $query ) {
    if ( 'post' !== $query->get( 'post_type' ) {
        return $joins;
    }

    if ( ! $query->get( 's' ) ) {
        return $joins;
    }

    $joins .= 'JOIN mycustomtable ON( wp_posts.ID = mycustomtable.post_id )';
    return $joins;
}, 10, 2 );
Other Filters

While the above two filters make up the bulk of useful filters for most cases, the following may also be relevant depending on your use-case:

  • posts_where_paged and posts_join_paged – Similar to the above two filters, but used expressly when pagination is concerned
  • posts_orerby – Direct access to the ORDER BY clause, responsible for the order in which your posts appear when queried
  • post_limits – The query’s LIMIT clause
  • posts_fields – Determines which fields are returned from the database (note that WordPress does a lot of this for you when it gets the actual post objects, typically you wouldn’t need to modify this outside of very specific use-cases)
  • posts_clauses – This is kind of a catchall for various parts of the query, including WHERE, GROUP BY, JOIN, ORDER BY, DISTINCT, selected fields, and LIMIT.

Conclusion

With the above, I hope you have a better understanding of how and where WordPress filters certain things, and I hope you go off and explore the codebase the next time you say, “Geez, I really wish I could modify that!” WordPress has plenty of actions for developers to tap into once you know where to look. Happy coding!

The post Debugging WordPress Core: Actions and Filters appeared first on WebDevStudios.

]]>
https://webdevstudios.com/2019/01/22/debugging-wordpress-core-actions-filters/feed/ 3 19386
Getting Started with WordPress Hooks https://webdevstudios.com/2015/11/12/getting-started-wordpress-hooks/ https://webdevstudios.com/2015/11/12/getting-started-wordpress-hooks/#respond Thu, 12 Nov 2015 17:16:36 +0000 http://webdevstudios.com/?p=11925 It is well-known that WordPress is one of the most popular Content Management Systems available for building and maintaining a website. While a big part of that success is due to the ease-of-use, another big part of that is the wide variety of themes and plugins that are available to make WordPress do just about anything Read More Getting Started with WordPress Hooks

The post Getting Started with WordPress Hooks appeared first on WebDevStudios.

]]>
It is well-known that WordPress is one of the most popular Content Management Systems available for building and maintaining a website. While a big part of that success is due to the ease-of-use, another big part of that is the wide variety of themes and plugins that are available to make WordPress do just about anything you can think of. What gives plugins and themes their true power is the Plugin API, which is a set of Actions and Filters, collectively known as Hooks, that are embedded within the core WordPress codebase. The goal of this post is to demystify the WordPress Hooks and enable you to start diving in and using them.

An Example — LEGO®

Most people have at least heard of, if not directly experienced, LEGO® toy bricks. With these toys, you can follow instructions in a set and build your own toys to play with. You can also creatively build something entirely new using your own imagination. Or you can combine the two approaches and customize a pre-designed set with your own modifications. All of this is made possible by the way that individual LEGO® pieces fit together: the bumps on the top and the gaps on the bottom, as well as a few other types of connections.

legos, lego building, lego gif, WordPress hooks, what are WordPress hooks, learning about WordPress, learning about WordPress hooks, WordPress 101, WordPress development, learn to be a WordPress developer

In our analogy, the bumps and gaps in the LEGO® bricks work the same way as Hooks in WordPress. You can add new functionality, remove default functionality, or just modify the current piece of data. Getting a firm understanding of this process will help you start to unlock the real power of WordPress.

Types of Hooks

There are two types of Hooks in WordPress: Actions and Filters. They work almost the same way, but it’s important to understand how they are different. Actions intend for you to simply do something, while filters intend for you to change something. Also, actions may provide variables to your code to give you more context, while filters will always provide at least one variable: the data you’re modifying.

Actions

Actions are used to insert your own code into a specific point of the WordPress core execution. Actions in WordPress look like this:

    /**
     * Print scripts or data in the head tag on the front end.
     *
     * @since 1.5.0
     */
    do_action( 'wp_head' );

The function do_action() is used to trigger an action. In the example above, “wp_head” is the name of the action. If you want to hook into this action, you’ll need to know the name of the action so that WordPress runs your code in the correct place.

Filters

Just like actions, filters are used to insert your own code into a specific point of the WordPress core execution. What makes them different from actions is that filters always provide data to your code, and they always expect your code to provide data back again. Filters in WordPress typically look like this:

    /**
     * Filter the list of CSS body classes for the current post or page.
     *
     * @since 2.8.0
     *
     * @param array  $classes An array of body classes.
     * @param string $class   A comma-separated list of additional classes added to the body.
     */
    $classes = apply_filters( 'body_class', $classes, $class );

The function apply_filters() is used to trigger a filter. In the example above, “body_class” is the name of the filter, and there are two variables passed in for context: $classes and $class. The result of the filter is assigned to the $classes variable. This is essentially how the filter works: A piece of data is passed through the filter and the filtered version is used by WordPress. In this case, the piece of data is the variable $classes.

Using Hooks

Adding your own functionality to the WordPress hooks is very straightforward. You’ll need the add_action() function for actions, and the add_filter() function for filters. Let’s get into some practical examples using the two hooks we mentioned before: “wp_head” and “body_class”. In each of the cases below, we’ll give a code example, and then explain what’s happening in that example.

Actions

/**
 * Add our own output to the HTML <head> element.
 */
function jpry_wp_head_action() {
    echo '<!-- This comment will show up inside the HTML <head> element! -->';
}
add_action( 'wp_head', 'jpry_wp_head_action', 10, 0 );

In the code above, you can see that we’re calling the add_action() function with four parameters. The parameters are:

  1. 'wp_head' — This is the name of the hook that we want to tie into.
  2. 'jpry_wp_head_action' — This is the name of our function that WordPress should execute. In my example, you can see that it corresponds to a function name.
  3. 10 — This is the priority level of our function.
  4. 0 — This is the number of arguments (parameters) that our function (jpry_wp_head_action()) expects to receive.

In this example, I’m simply adding an HTML comment to the <head> block. When you view the source of the site, you will be able to see this comment within the <head> block after the code is added.

While I’ve included all of the available parameters in this example, it’s important to note that the last two parameters are optional. You should only include them if you want to override the default value that WordPress uses. I will explain this in more detail in the Optional Parameters section below.

Filters

/**
 * Add the "jpry" class to the HTML <body> element.
 *
 * @param array $classes The array of body classes.
 * @return array The modified array of classes.
 */
function jpry_body_class_filter( $classes ) {
    $classes[] = 'jpry';

    return $classes;
}
add_filter( 'body_class', 'jpry_body_class_filter', 10, 1 );

Just like the add_action() function, we call the add_filter() function with 4 parameters:

  1. 'body_class' — This is the name of the hook that we want to tie into.
  2. 'jpry_body_class_filter' — This is the name of our function that WordPress should execute. Just like above, it corresponds to the function name that I created just above it.
  3. 10 — The priority level.
  4. 1 — The number of arguments that our function (jpry_body_class_filter()) expects to receive.

As I mentioned before about filters, they always expect to pass you data, and they always expect you to return data when you’re done. In the above example, you can see that my function jpry_body_class_filter() accepts a single parameter, named $classes. I know from looking at the documentation of this filter that $classes is an array of class names. I’m adding my own class and so I simply append my own data to the existing array. In order for this to work, it’s absolutely crucial to return the $classes variable back to the filter.

Not returning data back to a filter is a common cause of obscure bugs and support requests. Without the return statement, not only will your filter not add your data, but it will also cause the existing data to be empty! If you’re using a filter and you don’t seem to be getting any data back when you’re done, or if you’re seeing some strange bug somewhere that is hard to figure out, double-check to make sure you’ve included that return statement.

Optional Parameters

I previously mentioned that there are two optional parameters with each of these functions: priority and number of arguments.

The priority parameter does exactly what it says: it prioritizes your function with everything else that has been added to that same hook. WordPress executes functions in lowest-to-highest priority. When multiple functions have been added at the same priority level, then they are executed in the order that they were added.

The number of arguments parameter ensures that your function receives the correct number of parameters when it is called. This is relevant when you’re using an action or filter that passes more than one value. If we look at the body_class filter definition above, we can see that there are two pieces of data available to us: $classes and $class. While we don’t need both pieces of data for our specific use, there may be other developers who do need the extra data. In order to have the second variable passed to their function so that they can use it, they would set the number of arguments to 2 instead of 1.

jeremy pry, WebDevStudios, about WordPress hooks, WordPress hooks, learning WordPress hooks, what are WordPress hooks, WordPress Development, WordPress dev 101, WordPress 101, Learning WordPress, WordPress developers

Resources

Hooks are an integral part of how WordPress functions, and so there are plenty of resources available to help you utilize them:

Do you have any questions or comments? Did you find an error, or think I missed something? Leave a comment below!

The post Getting Started with WordPress Hooks appeared first on WebDevStudios.

]]>
https://webdevstudios.com/2015/11/12/getting-started-wordpress-hooks/feed/ 0 11925