The Wonderful World of Actions & Filters

Who is this Josh?

Alternatively, an Introduction

Outline

  1. The Basics
     
  2. Common code patterns
     
  3. Action-driven themes
     
  4. An extreme use case

tl:dr;

USE CUSTOM ACTIONS AND FILTERS LIKE CRAZY

1. The Basics

do_action and apply_filters

Common Actions

  1. wp_head and wp_footer
     
  2. init and wp
     
  3. pre_get_posts

Common Filters

  1. body_class
     
  2. the_title
     
  3. 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

  1. remove_filter / remove_action
     
  2. has_filter / has_action
     
  3. current_filter
     
  4. 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

  1. after_body_open
     
  2. before_main_div, after_main_div
     
  3. before_content_div, after_content_div
     
  4. before_index, after_index
     
  5. before_loop, after_loop
     
  6. before_content, after_content

4. Extreme Use Cases

Preloading Markup

with save_post

Preloading markup with save_post

  1. Post type has many post meta items
     
  2. Excessive database queries are slowing page load times
     
  3. Hook into save_post and run front-end markup there, and save to a transient.
     
  4. BEWARE: make sure you have a cache-clearing method

What if we could filter everything?

The Layouts

  1. Content
  2. Blurb
  3. Blurblist
  4. Gallery
  5. Map
  6. Header
  7. Post List
  8. Slideshow
  9. Accordion
  10. 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

  1. Hooks can be used to solve many problems
     
  2. Look for hooks in WordPress and the plugins or themes you use
     
  3. 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