WordPress filters and anonymous functions

Anonymous functions have been around for a long time. And since WordPress now supports php 5.6 it can be safely used.
And appears to be allowed?

Personally I’m not a fan of anonymous functions in combination with WordPress actions and filters. My main concern is you can’t remove them once registered. How ever today I found a use case which was very usefull in combination with the use

My example:

<?php
/* Template name: some-template */

// Gather all data
$condition_for_title = true;
$h1_title_override = 'very heavy and complicated check';
// the H1 also needed as the <title>

add_filter( 'pre_get_document_title', function( $title ) use ($h1_title_override, $condition_for_title) {
    if ($condition_for_title) {
        return $h1_title_override;
    }
    return $title;
}, 20, 1 );

get_header();

// start body
?>
    <h1><?php echo $h1_title_override ?>

Here I pass 2 variables h1_title_override and $condition_for_title which are created outside the function. In my case these where quite complicated and heavy checks. Of course I could put those in a function and cache the result. And call that check in the filter function. But still I need to check the current template before doing the function.

More traditional Example:

in functions.php

function complicated_check() {
    // Gather all data
    $condition_for_title = true;
    $h1_title_override   = 'very heavy and complicated check';

    return [
        'condition_for_title' => $condition_for_title,
        'h1_title_override'   => $h1_title_override,
    ];
}

function title_exception_for_template( $title ) {
    if ( ! is_page_template('clean-template.php')) {
        return $title;
    }

    $template_data = complicated_check();

    if ( $template_data['condition_for_title'] ) {
        return $template_data['h1_title_override'];
    }

    return $title;
}

add_filter( 'pre_get_document_title', 'title_exception_for_template', 20, 1 );

in clean-template.php

<?php
/* Template name: clean-template */
$template_data = complicated_check();

get_header();

// start body
?>
    <h1><?php echo $template_data['h1_title_override'] ?>

Both these approaches do the same thing. But the more traditional way is a lot more code. Although it has cleaner template.
I probably won’t use this much. If the anonymous function was more complicated it will get hard to read.

But for this case I think it was neat that I could use this little feature.

Meta query order

The site had 20K plus posts/pages/ect, freshly put live.

The hoster called. It had slow queries, 9 seconds on average. A lot, multiple per minute. The same query over and over again.
It was a pretty normal post table query. It had one part extra.
A meta query was to exclude all spot_closed posts. When I removed that it was quick. It didn’t show anything special.

The meta query was added as such:

$meta_query_spots = [
    'relation'     => 'OR',
    [
        'key'     => 'spot_closed',
        'value'   => 1,
        'compare' => '!=',
    ],
    [
        'key'     => 'spot_closed',
        'compare' => 'NOT EXISTS',
    ],
];

Check if the spot is closed, and if it a spot does not have a close status.
The fix, flip the checks. And bam query less then half a second.

WordPress can’t use number as page names

Where is what we wanted. A page example.com/404
So we had a page in WordPress so the content was controllable.

But on save the slug was changed to 404-2.

I figured maybe that slug was preserved. So after some digging I found this beautiful *ahem* if statement.

if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug )  || apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent ) ) { //.......

The part it fails on is the preg_match. No number is accepted.
So it’s not just the slug 404 but every number.

The reason for this because it preserves numbers for pagination (including in-page pagination).

So the solution was to rename the page to example.com/404-page

Source: wp_unique_post_slug

WordPress Action Priority

I wanted to add_action between 2 different actions.
These actions where priority 3 and 4. So I tried 3.5. That didn’t work.

Decimals get rounded down. What did work was the string “3.5”

This got me curious about strings and the sorting in general. Under the hood WP just uses array_keys
To make things easier here is a list of use cases:

WordPress action how a priority is sorted

ACF load user_meta cross WordPress multisite

Users are global in a multisite. And default ACF groups are per site. So if you set a pinterest url for a user on one site it will apply it for all sites. No problem. But if you set an image it will try to find that image-id on each site.

The solution.

Step 1; Limit the user acf groups only to the main site:

function wpstarter_filter_fields_group($result, $rule, $screen) {
    if ( isset( $screen['user_id'] ) && ! is_main_site() )
        return false;

    return $result;
}
add_filter('acf/location/rule_match', 'wpstarter_filter_fields_group', 10, 3);

Step 2; Get image fields for users only from the main site:

function wpstarter_load_avatar_value_cros_site( $value, $post_id, $field ) {

    if( 0 !== strpos( $post_id, 'user_' ) || is_main_site() ) {
        return $value;
    }

    switch_to_blog( get_main_site_id() );

    $value_of_main_site = acf_get_value($post_id, $field);
    $value              = acf_format_value($value_of_main_site, $post_id, $field);

    restore_current_blog();
    return $value;
}
add_filter( 'acf/format_value/type=image', 'wpstarter_load_avatar_value_cros_site', 11, 3);

Add custom post_meta to the WordPress rest API

So I needed some metadata to be in the rest API of a custom post_type.
The following adds the subtitle to the book post_type

add_action('rest_api_init', function () {
    register_rest_field('book', 'subtitle', [
        'get_callback'    => 'get_meta_data_for_rest',
        'update_callback' => null,
        'schema'          => null,
    ]);

});

function get_meta_data_for_rest($post, $field_name, $request)
{
    // it's not always an object
    if (isset($post->id) && ! empty($post->id)) {
        $post_id = $post->id;
    } elseif (isset($post['id']) && ! empty($post['id'])) {
        $post_id = $post['id'];
    } else {
        return null;
    }

    return get_post_meta($post_id, $field_name, true);
}

Delete all empty taxonomie terms in WordPress

The code below will find and delete all empty terms of every taxonomie, including the menu.
Think before you delete, you might want to keep some empty categories.

foreach ( get_taxonomies() as $tax_slug ) {//
    $terms = get_terms( $tax_slug, array( 'hide_empty' => false ) );

    /** @var WP_Term $wp_term */
    foreach ( $terms as $wp_term ) {
        if ( 0 == $wp_term->count ) {
            wp_delete_term( $wp_term->term_id, $wp_term->taxonomy );
        }
    }
}