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 );
        }
    }
}