Posted on Leave a comment

How Does add_filter Work In WordPress

Filters are the source of confusion when I first started my WP development. I only gained a better understanding of this topic until recently. Thus, I want to share with you my finding.

As you may already know (I hope), WordPress users can modify parts of WordPress website with filter. You use the add_filter function to add your own function to modify a particular item. For example, you want to add a few words before the title of every page/post of your blog, you can do like this:

add_action('the_title', 'modify_the_title');

‘modify_the_title’ is the function you need to define to do the modifications.

Think of functions like ‘modify_the_title’ as a blackbox

The $wp_filter variable

To understand how add_filter works, you need to know a global variable called $wp_filter. It’s an array. Let’s see how is it structured.

Since it’s a global variable, we can access it anywhere. Let’s verify it’s actually an array by entering this code in our current active theme’s functions.php

echo '<h1>$wp_fitler type</h1>';
echo gettype($wp_filter);

echo '<h1>Number of elements in $wp_fitler</h1>';
echo count($wp_filter);

echo '<h1>List of hooks in $wp_filter</h1>';

foreach ($wp_filter as $key => $value)
{
    echo "<p>$key</p>";
}
die();

Open the website, we’ll see this:

As you can see, it’s an array with 658 elements. With the foreach loop, you can also see that it’s an associative array with the key is the name of the hooks.

So, I want to see the details of ‘the_title’ hook. Let’s replace the above code with this one:

echo '<h1>the_title hook details</h1>';
echo '<h3>type</h3>';
echo gettype($wp_filter['the_title']);
echo '<h3>details</h3>';
dump($wp_filter['the_title']);


die();

I also installed var_dumper, a very nice library to pretty print the var_dump to the screen. Here is what we have:

As you can see, $wp_filter is an associative array where the keys are the name of the hook and the values are WP_Hook objects. The most important part here is the callbacks field. It’s an array contains the details of functions that registered to the hook.

In case you are curious, callbacks is an associative array (not indexed). What the number 10, 11 means is the order of execution.

How custom functions are added to $wp_filter

If you read the documentation of add_filter or add_action, the third argument is the priority or execution. The larger the number, the later it will be executed.

So, if I add the following function to the_title hook (notice that I set the priority to 1):

add_filter('the_title', 'my_custom_function', 1, 0);

function my_custom_function($title)
{
    return $title;
}

And we check the dump of the_title hook object again:

You can see that callbacks now has another element with key is ‘1’ that store the newly added function.

Adding functions to your own filters

I usually wonder, how filters hooked get registered at the first place. Do we have a function called ‘register_filter’?

No, there isn’t a function like that. When you call the add_filter function, if the filter name doesn’t exists in $wp_filter, it will be added to the $wp_filter array. Let me demonstrate.

add_filter('filter_hook_that_does_not_exist', 'my_custom_function', 1, 0);

function my_custom_function($input)
{
    /** code to modify the input here */
    return $input;
}

echo '<h1>hook details</h1>';
echo '<h3>type</h3>';
echo gettype($wp_filter['filter_hook_that_does_not_exist']);
echo '<h3>details</h3>';
dump($wp_filter['filter_hook_that_does_not_exist']);
die();

As you can see, we add a filter to a non-existence hook. Let’s see the result:

So, how the function ‘my_custom_function’ get executed? Well, that’ the topic of the post about apply_filters.

Conclusion

Hopefully now you are like me, understand how are custom functions get added to the $wp_filter every time we call add_filter function. If you have questions, please let me know

Posted on Leave a comment

How Are WordPress Post/Page’s Title Got Rendered

This post is another effort of mine in understanding the core of WordPress. Hopefully at the end of this post, you’ll gain something about. My goal is to have better understanding of the way WP render particular parts of the website by using the filters.

Dive into the header.php file of WP theme

I’m using store front theme on my local dev site. However, you’ll encounter the same layout/code in any other themes.

This is the part of the code that renders the header

<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2.0">
<link rel="profile" href="http://gmpg.org/xfn/11">
<link rel="pingback" href="<?php bloginfo( 'pingback_url' ); ?>">

<?php wp_head(); ?>
</head>

As you can see, there is no title tag. However, there is a piece of PHP code that generates the title and other stuffs. That’s wp_head()

Dive into wp_head function

The code for wp_head is surprisingly simple:

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

It calls another function called do_action with the parameter ‘wp_head’. I hope you have, at least a vague understanding of how filters and actions work in WordPress.

Now, we need to know where the title’s filters are added to the wp_head hook. With the help of PhpStorm, I was able to locate this part:

add_action( 'wp_head', '_wp_render_title_tag', 1 );

As you can see, there is a function that responsible for rendering the title called ‘_wp_render_title_tag’. Let’s see what does it do:

function _wp_render_title_tag() {
	if ( ! current_theme_supports( 'title-tag' ) ) {
		return;
	}

	echo '<title>' . wp_get_document_title() . '</title>' . "\n";
}

Once again, the function is very short since it calls other functions. Our next step would be investigating the function ‘wp_get_document_title’

function wp_get_document_title() {

	/**
	 * Filters the document title before it is generated.
	 *
	 * Passing a non-empty value will short-circuit wp_get_document_title(),
	 * returning that value instead.
	 *
	 * @since 4.4.0
	 *
	 * @param string $title The document title. Default empty string.
	 */
	$title = apply_filters( 'pre_get_document_title', '' );
	if ( ! empty( $title ) ) {
		return $title;
	}

	global $page, $paged;

	$title = array(
		'title' => '',
	);

	// If it's a 404 page, use a "Page not found" title.
	if ( is_404() ) {
		$title['title'] = __( 'Page not found' );

		// If it's a search, use a dynamic search results title.
	} elseif ( is_search() ) {
		/* translators: %s: search phrase */
		$title['title'] = sprintf( __( 'Search Results for “%s”' ), get_search_query() );

		// If on the front page, use the site title.
	} elseif ( is_front_page() ) {
		$title['title'] = get_bloginfo( 'name', 'display' );

		// If on a post type archive, use the post type archive title.
	} elseif ( is_post_type_archive() ) {
		$title['title'] = post_type_archive_title( '', false );

		// If on a taxonomy archive, use the term title.
	} elseif ( is_tax() ) {
		$title['title'] = single_term_title( '', false );

		/*
		* If we're on the blog page that is not the homepage or
		* a single post of any post type, use the post title.
		*/
	} elseif ( is_home() || is_singular() ) {
		$title['title'] = single_post_title( '', false );

		// If on a category or tag archive, use the term title.
	} elseif ( is_category() || is_tag() ) {
		$title['title'] = single_term_title( '', false );

		// If on an author archive, use the author's display name.
	} elseif ( is_author() && $author = get_queried_object() ) {
		$title['title'] = $author->display_name;

		// If it's a date archive, use the date as the title.
	} elseif ( is_year() ) {
		$title['title'] = get_the_date( _x( 'Y', 'yearly archives date format' ) );

	} elseif ( is_month() ) {
		$title['title'] = get_the_date( _x( 'F Y', 'monthly archives date format' ) );

	} elseif ( is_day() ) {
		$title['title'] = get_the_date();
	}

	// Add a page number if necessary.
	if ( ( $paged >= 2 || $page >= 2 ) && ! is_404() ) {
		$title['page'] = sprintf( __( 'Page %s' ), max( $paged, $page ) );
	}

	// Append the description or site title to give context.
	if ( is_front_page() ) {
		$title['tagline'] = get_bloginfo( 'description', 'display' );
	} else {
		$title['site'] = get_bloginfo( 'name', 'display' );
	}

	/**
	 * Filters the separator for the document title.
	 *
	 * @since 4.4.0
	 *
	 * @param string $sep Document title separator. Default '-'.
	 */
	$sep = apply_filters( 'document_title_separator', '-' );

	/**
	 * Filters the parts of the document title.
	 *
	 * @since 4.4.0
	 *
	 * @param array $title {
	 *     The document title parts.
	 *
	 *     @type string $title   Title of the viewed page.
	 *     @type string $page    Optional. Page number if paginated.
	 *     @type string $tagline Optional. Site description when on home page.
	 *     @type string $site    Optional. Site title when not on home page.
	 * }
	 */
	$title = apply_filters( 'document_title_parts', $title );

	$title = implode( " $sep ", array_filter( $title ) );
	$title = wptexturize( $title );
	$title = convert_chars( $title );
	$title = esc_html( $title );
	$title = capital_P_dangit( $title );

	return $title;
}

This function is quite lengthy since it does all of jobs of generating the title. You can also see that, while the title tag is a small part of the document, this function isn’t a simple one at all. It’s partly because WordPress offers so much flexibility for developers to modify the titles and other parts of the content.

As you read through the code, you’ll see that this function checks for what kind of page it’s currently on (whether it’s a homepage/archive page…) and render the title for such pages accordingly.

You may have some confusions regarding the apply_filters part. That’s the topic of another post which I’m going to write shortly.