The Wonderful World of Actions & Filters
Who is this Josh?
Alternatively, an Introduction
Outline
- The Basics
- Common code patterns
- Action-driven themes
- An extreme use case
tl:dr;
USE CUSTOM ACTIONS AND FILTERS LIKE CRAZY
1. The Basics
do_action and apply_filters
Common Actions
- wp_head and wp_footer
- init and wp
- pre_get_posts
Common Filters
- body_class
- the_title
- the_content
Two examples
<?php
function modify_body_class( $classes ) {
$current_post = get_post();
if ( is_singular() ) {
$classes[] = $current_post->post_name;
}
return $classes;
}
add_filter('body_class', 'modify_body_class', 10, 1);
function add_favicon() {
echo '<link href="/favicon.ico" rel="icon">';
}
add_action('wp_head', 'add_favicon', 10);
add_action('admin_head', 'add_favicon', 10);
Supporting Functions
<?php
remove_filter( 'body_class', 'modify_body_class', 10 );
remove_action('wp_head', 'add_favicon', 10 );
if ( ! has_filter( 'the_content', 'example_alter_the_content' ) ) {
add_filter( 'the_content', 'prefix_alter_the_content' );
}
function change_title( $title, $id ) {
echo current_filter(); // 'the_title'
return $title;
}
add_filter( 'the_title', 'change_title', 10, 2 );
if ( doing_action( 'save_post' ) ) {
// Do Something Here
}
Supporting Functions
- remove_filter / remove_action
- has_filter / has_action
- current_filter
- doing_filter / doing_action
The problem:
Thinking with conditional tags instead of filters
is_front_page()
is_home()
is_admin()
is_network_admin()
is_admin_bar_showing()
is_single()
is_sticky()
is_post_type_hierarchical( $post_type )
is_post_type_archive()
is_comments_popup()
comments_open()
pings_open()
is_page()
is_page_template()
is_category( $category )
is_tag()
is_tax()
has_term()
term_exists( $term, $taxonomy, $parent )
is_taxonomy_hierarchical( $taxonomy )
taxonomy_exists( $taxonomy )
is_author()
is_date()
is_year()
is_month()
is_day()
is_time()
Conditional Tags
<div class="section-header">
<div class="section-image">
<div class="content-area">
<h1><?php the_title(); ?></h1>
</div>
</div>
</div>
<div class="section-header">
<div class="section-image section-image--large">
<div class="content-area">
<h1>The Aquatic Blog</h1>
</div>
</div>
</div>
Pages
Blog
Stay DRY and Avoid Opinionated Code
<?php
function section_title() {
$title = '';
if ( is_page() ) {
$title = get_the_title();
}
if ( is_home() ) {
$title = 'The Aquatic Blog';
}
return $title;
}
?>
<div class="section-header">
<div class="section-image">
<div class="content-area">
<h1><?php section_title(); ?></h1>
</div>
</div>
</div>
Solving for the DRY principle
<footer id="colophon" class="site-footer">
<?php get_template_part( 'template-parts/footer/footer', 'widgets' ); ?>
<div class="site-info">
<?php $blog_info = get_bloginfo( 'name' ); ?>
<?php if ( ! empty( $blog_info ) ) : ?>
<a class="site-name" href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a>,
<?php endif; ?>
<a href="<?php echo esc_url( __( 'https://wordpress.org/', 'twentynineteen' ) ); ?>" class="imprint">
<?php
/* translators: %s: WordPress. */
printf( esc_html__( 'Proudly powered by %s.', 'twentynineteen' ), 'WordPress' );
?>
</a>
<?php
if ( function_exists( 'the_privacy_policy_link' ) ) {
the_privacy_policy_link( '', '<span role="separator" aria-hidden="true"></span>' );
}
?>
</div><!-- .site-info -->
</footer><!-- #colophon -->
What is opinionated code?
<?php
function section_title() {
$title = '';
if ( is_page() ) {
$title = get_the_title();
}
return apply_filters('aam_section_title', $title);
}
function set_blog_title( $title ) {
if ( is_home() || is_singular( 'post' ) || is_tag() || is_category() ) {
$title = 'The Aquatic Blog';
}
return $title;
}
add_filter( 'aam_section_title', 'set_blog_title', 10, 1);
Stay DRY and flexible
2. Common Design Patterns
Naming Conventions
<?php
// Separation characters
apply_filters( 'the_content', $content );
// Namespacing
do_action( 'acf/input/admin_enqueue_scripts' );
// Variable-based names
apply_filters( 'wpseo_' . $rel . '_rel_link', $link );
Naming Conventions
<?php
/**
* Class LayoutDefault
*/
class LayoutDefault {
public static function hook_wordpress() {
add_filter( 'body_class', [ __CLASS__, 'body_class' ], 20, 1 );
}
public static function body_class( $classes ) {
$current_post = get_post();
if ( is_singular() ) {
$classes[] = $current_post->post_name;
}
return $classes;
}
}
LayoutDefault::hook_wordpress();
remove_filter( 'body_class', 'LayoutDefault\body_class', 20 );
Documentation
<?php
// WooCommerce, /templates/content-single-product.php
/**
* Hook: woocommerce_single_product_summary.
*
* @hooked woocommerce_template_single_title - 5
* @hooked woocommerce_template_single_rating - 10
* @hooked woocommerce_template_single_price - 10
* @hooked woocommerce_template_single_excerpt - 20
* @hooked woocommerce_template_single_add_to_cart - 30
* @hooked woocommerce_template_single_meta - 40
* @hooked woocommerce_template_single_sharing - 50
* @hooked WC_Structured_Data::generate_product_data() - 60
*/
do_action( 'woocommerce_single_product_summary' );
Boolean Filters
<?php
function turn_off_default_css( $display_css ) {
return false;
}
add_filter('some_plugin_display_css','turn_off_default_css', 10, 1 );
How to Find Hooks
<?php
function the_title( $before = '', $after = '', $echo = true ) {
$title = get_the_title();
if ( strlen($title) == 0 )
return;
$title = $before . $title . $after;
if ( $echo )
echo $title;
else
return $title;
}
How to Find Hooks
<?php
function get_the_title( $post = 0 ) {
$post = get_post( $post );
$title = isset( $post->post_title ) ? $post->post_title : '';
$id = isset( $post->ID ) ? $post->ID : 0;
/* Miscellaneous logic.. */
/**
* Filters the post title.
*
* @since 0.71
*
* @param string $title The post title.
* @param int $id The post ID.
*/
return apply_filters( 'the_title', $title, $id );
}
3. Action-Driven Themes
<?php
class Interior {
public static function init() {
add_action( 'wp', function () {
if ( ! is_front_page() ) {
self::hook_wordpress();
}
});
}
public static function hook_wordpress() {
add_action( 'before_wrapper_div', [ __CLASS__, 'get_header_section' ], 20 );
add_action( 'after_loop', [ __CLASS__, 'pagination' ], 20 );
add_filter( 'after_content_div', [ __CLASS__, 'get_sidebar' ], 20, 1 );
}
public static function get_header_section() {
get_template_part( 'src/partials/header-section/header-section' );
}
public static function pagination() {
the_posts_pagination();
}
public static function get_sidebar() {
get_sidebar();
}
}
Interior::init();
Suggested Actions
- after_body_open
- before_main_div, after_main_div
- before_content_div, after_content_div
- before_index, after_index
- before_loop, after_loop
- before_content, after_content
4. Extreme Use Cases
Preloading Markup
with save_post
Preloading markup with save_post
- Post type has many post meta items
- Excessive database queries are slowing page load times
- Hook into save_post and run front-end markup there, and save to a transient.
- BEWARE: make sure you have a cache-clearing method
What if we could filter everything?
The Layouts
- Content
- Blurb
- Blurblist
- Gallery
- Map
- Header
- Post List
- Slideshow
- Accordion
- Tabs
The Template Library
<?php
public function __construct( $name = '', $data = null, $content = '',
$tag = 'div', array $attributes = [], array $structure = [],
$parent_name = '', $separator = '__', $before = '', $prepend = '',
$append = '', $after = '' ) {
$this->name = $name;
$this->tag = $tag;
$this->attributes = $attributes;
$this->content = $content;
$this->data = $data;
$this->structure = $structure;
$this->parent_name = $parent_name;
$this->separator = $separator;
$this->before = $before;
$this->prepend = $prepend;
$this->append = $append;
$this->after = $after;
}
Basic Organisms
<?php
class Link extends Organism {
public function __construct( $name = 'link', $href, $content = '' ) {
parent::__construct( $name, $data = null, $content, $tag = 'a' );
$this->attributes['href'] = $href;
}
}
class LinkFrontPage extends Link {
public function __construct( $name = 'link-front-page', $content = '' ) {
parent::__construct( $name, $href = get_site_url(), $content );
if ( '' === $this->content ) {
$this->content = get_bloginfo( 'name' );
}
}
}
Use Case:
Pardot Landing Pages
Keeping things editable
<?php
function add_pardot_region_attribute( $object ) {
$object->attributes['pardot-region'] = uniqid( $object->name . '-' );
return $object;
}
add_filter( 'hero__title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'hero__image', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'hero__description', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'blurblist__list-title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'blurblist__blurb-title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'blurblist__blurb-content', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'cta-bar__title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-blurb__title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-blurb__subtitle', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-blurb__content', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-gallery__image', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-gallery__caption', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'colophon__copyright', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'colophon__address', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'colophon__phone', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'solo-headline', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'video', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'intro__title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-content', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-header__title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-accordion__panel__content-image', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-accordion__panel__content-subtitle', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'acf-accordion__panel__content-text', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'pardot-form__title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'callout__image', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'callout__title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'callout__content', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'explore__list-title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'explore__blurb-title', 'add_pardot_region_attribute', 20, 1 );
add_filter( 'explore__blurb-content', 'add_pardot_region_attribute', 20, 1 );
The Output
<div class="hero--isLeftAligned hero">
<div class="hero__background">
<img pardot-region="hero__image-5be4aa65295e9" pardot-region-type="image" width="640" height="298" src="..." class="hero__image" alt="" />
</div>
<div class="hero__text">
<div pardot-region="hero__title-5be4aa652c329" pardot-region-type="simple" class="hero__title">...</div>
<div pardot-region="hero__description-5be4aa652c403" pardot-region-type="simple" class="hero__description">
...
</div>
<a href="#cta" class="hero__link"><span pardot-region="hero__link-5be4aa652c4fb" pardot-region-type="simple">...</span></a>
</div>
</div>
In Conclusion
Takeaways
- Hooks can be used to solve many problems
- Look for hooks in WordPress and the plugins or themes you use
- Add hooks to your own code as much as possible
That’s all, folks!
Any questions?
The Wonderful World of Actions & Filters
By Josh Nederveld
The Wonderful World of Actions & Filters
- 493