Shadow Posttype

The case I was dealing with. There is a posttype “person” and it had the normal templates, a archive-person.php and single-person.php with a bunch of metadata.
Which where visible on example.com/person/john-doe and the archive example.com/persons.
So far nothing special.

Every “person” was a -well- person. Some persons where also a “Judge”. A judge was an extra metabox on the person edit page.
And it needed it’s own pages on the frontend. example.com/judge/john-doe and example.com/judges.

Adding the rewrites

<?php
function judge_rewrite_rule() {
	/**
	 * Single judge
	 * 
	 * set is_judge to 1
	 * set person to post slug
	 */
	add_judge_rewrite_rule( '^judge/([^/]+)/?', 'index.php?is_judge=1&person=$matches[1]', 'top' );

	/**
	 * Archive for judges
	 */
	add_rewrite_rule( '^judges/?', 'index.php?is_judge=1&post_type=person', 'top' );
}
add_action( 'init','rewrite_rule' );

function judge_rewrite_tag() {
	/**
	 * make the query_vars aware of `is_judge`
	 */
	add_rewrite_tag( '%is_judge%', '([0-9]+)' );
}
add_action( 'init', 'judge_rewrite_tag' );

The main 2 functions to look out for are add_rewrite_rule and add_rewrite_tag.
I register the query_var is_judge and set it when the url is /judges/ or /judge/john-doe

After adding this do flush the permalinks. Just go to the settings for permalinks and press save. No need to change anything.

Registering the templates

<?php
/**
 * Set the correct theme/template-file.php
 * the shadow single & archive.
 *
 * @param string $template
 *
 * @return string
 */
function assign_judge_template( $template ) {
	if ( 1 !== (int) get_query_var( 'is_judge' ) ) {
		return $template; // we only check for templates when the judge set.
	}

	// assign the correct template.
	if ( is_single() ) {
		return get_template_directory() . '/single-person-judge.php';
	}
	if ( is_post_type_archive( 'person' ) ) {
		return get_template_directory() . '/archive-person-judge.php';
	}


	return $template; // fallback.
}
add_filter( 'template_include', 'assign_judge_template', 10, 1 );

When the is_judge is set set a different template. In this case this these templates are in the theme folder. You could also set templates from a plugin. The naming is can be anything but I picked single-person-judge.php so the next person can easier find the relation to the post type.

Setting the correct posts for the judge archive

Now the single will work as you would expect. But the archive page will still include all persons, not just the judges. Lets fix that.

<?php
/**
 * @param WP_Query $query
 */
function judge_pre_get_posts( $query ) {
	if ( 1 !== (int) $query->get( 'is_judge' ) || $query->is_single() ) {
		// only do this check if `is_judge` is set.
		// for single's this check is not needed.
		return;
	}
	// the meta_query
	$meta_is_judge = [
	    'relation' => 'AND',
		[ 'key'     => 'is_judge', 'compare' => 'EXISTS',],
		[
            'key'     => 'is_judge',
            'value'   => '1',
            'compare' => '=',
		],
	];

	// This part is to make sure you don't override other possible existing meta_queries
	$existing_meta   = $query->get( 'meta_query' );
	if ( empty( $existing_meta ) ) {
		$query->set( 'meta_query', $meta_is_judge );
	} else {
		$query->set( 'meta_query', [ 'relation' => 'AND', $existing_meta, $meta_is_judge, ] );
	}

}

add_action( 'pre_get_posts', 'judge_pre_get_posts' , 10, 1 );

If you’re familiar with the pre_get_posts hook this should not be to hard.
We select all posts that have the post_meta is_judge set to 1.
The way I add the meta_query seems a bit excessive, but this makes sure you never override a possible existing meta_query which an other filter might have added.

Now every Judge should be listed on example.com/judge and every individual judge should be visible on example.com/judge/john-doe. Only thing left to do is link to the page.

Linking to the judge url

As we want a person to be visible on both example.com/person/john-doe and example.com/judge/john-doe we can’t override the default permalink. So the best next thing is to create a helper function for calling the judge link.
This function should be used where you need to link to example.com/judge/john-doe, like on the judge archive page.

<?php
/**
 * Helper function
 *
 * @param int|WP_Post|null $post
 *
 * @return string, if not a judge, return normal permalink
 */
function get_judge_permalink( $post = null ) {
	$post = get_post( $post );
	if ( is_null( $post ) ) {
		return null;
	}

	// if the meta `is_judge` is not set, return the default person permalink.
	// for your use cases you might want to return something different.
	if ( '1' !== get_post_meta($post->ID, 'is_judge', true ) ) {
		return get_the_permalink( $post );
	}

	return home_url( 'judge/' . $post->post_name );
}

The function can be used in the same way as the regular get_the_permalink. get_judge_permalink(); the post argument is optional, and can also be a post_id.

Closing thoughts

In the template files, I suggest adding a very clear comment at the top, which explains where the rewrite functions are. An other person will otherwise have a hard time to find where this is created.

The list of judges will now automatically appear on the /judge/ archive page. If you need to create this list on a custom WP_query just add the query_var is_judge, example:

<?php
$q_args = array(  
	'post_type' => 'person',
	'is_judge' => 1, // this will get picked up by the `judge_pre_get_posts` filter
	// other arguments
);
$custom_query = new WP_Query($q_args);
// do what you want

Gravity Forms give editors access

By default Gravity forms isn’t accessible by editors. There are ways to do this with plugins like members.
As some might guess I prefer wp-cli for this:

wp cap add editor gravityforms_create_form gravityforms_edit_forms gravityforms_view_entries gravityforms_export_entries gravityforms_delete_entries graviyforms_delete_forms gravityforms_edit_entries gravityforms_view_entry_notes gravityforms_edit_entry_notes --grant
Difference in rights after setting capabilities

Running this will pretty much give all rights except the settings of Gravifty Forms.
It will allow creatign, editing & deleting forms. But also view entries and export them.

Full list of Gravity Forms Capabilities

Nano Shortcuts

I’ve know about nano and some it’s shortcuts. Today I explored them a bit more deeply.
So a list of shortcuts I find useful:

  • ctrl+K Cut the current line and put it in the nano clipboard (it’s not the same as the general clipboard)
  • ctrl+U Paste the line
  • ctrl+W Open search, type and hit enter. For the next match press alt+W
  • ctrl+Q Search backwards. For the next backward match press alt+Q
  • alt+U Undo
  • alt-E Redo
  • alt+C show the line number
  • alt-G Go to line number

Sources:

  • https://www.nano-editor.org/dist/latest/cheatsheet.html
  • https://askubuntu.com/a/672091/208343

PHP namespaces grouped

Consider the following code:

<?php
namespace abc;
use function is_string;
use function get_class;

class main {
    protected $string;
    public function __construct( $string ) {
        if ( is_string( $string )) {
            $this->string = $string;
        } else {
            $this->string = get_class( $this );
        }
    }
}

Nothing fancy. Just a class in a namespace using two functions from the global namespace (is_string & get_class).
Those two functions are imported from the global namespace as that will give a small performance boost.

But if you have 20-30 build in PHP functions that list will get very long….

Luckily you can merge them:

use function is_string, get_class;

For now I’m not sure I’ll always import build in PHP functions, the boost is small. And it’s annoying to keep track of.

sumcheck a whole directory

For some reason files changed on a server. Site down, always fun.
Restored a backup all good. This site did not have git on the server. But I still wanted to monitor the files for changes.

The one I landed on was:

find ./ -type f -name "*.php" -not -path "./wp-content/cache/*" -exec md5sum {} + | sort -k 2 | md5sum

Let’s dissect

What does this command do step by step

In the current directory and sub directory, list all files (not directories)

find ./ -type f

Limit it to php files

find ./ -type f -name "*.php"

Exclude the files in the caching directory, a bit weird syntax but it’s the one.

find ./ -type f -name "*.php" -not -path "./wp-content/cache/*"

For each file found run the command md5sum making a sum per file.

find ./ -type f -name "*.php" -not -path "./wp-content/cache/*" -exec md5sum {} +

Next we sort the output based on filepath+name.
We sort because find might return file order inconsistently.

find ./ -type f -name "*.php" -not -path "./wp-content/cache/*" -exec md5sum {} + | sort -k 2

Finally we create the grand total sumcheck based on all other sumchecks.

find ./ -type f -name "*.php" -not -path "./wp-content/cache/*" -exec md5sum {} + | sort -k 2 | md5sum

Sources:

  • https://stackoverflow.com/a/1658554/933065
  • https://unix.stackexchange.com/a/35834

WordPress the_date skipping same days

Lets take a look at this very basic loop.

<?php
$query_name = new WP_Query();

if ( $query_name->have_posts() ) :
    while ($query_name->have_posts()): $query_name->the_post();
        the_date();
        the_title();
        the_content();
    endwhile;
endif;

Nothing special right? Will by default just display the date, title and content of the first 10 posts.
But if 2 posts are published on the same day it will skip display that posts date.

When looking at the source code of the_date it compares the date of the previous post with the current using is_new_day.
I guess it can make sense in some scenario’s but too me it’s a bit weird by default.

To prevent this just use

<?php
echo get_the_date();

php shorthand if

I’ve known about the shorthand if for years:

<?php echo ($username) ? $username : ''; ?>

Today If saw this which is called Ternary and has been available since 5.3 which is older than my days of coding….

<?php echo ($username) ?: ''; ?>

Update PHP7.4
When assigning value especially in an array or object you can now do the following.

<?php

$person = [ 'date_of_birth' => '1970-01-01' ];

$person['name'] = $person['name'] ?? 'John Doe'; //php7.3
$person['name'] ??= 'John Doe';                  //php7.4

var_dump( $person );

for & foreach loops performance

Today we will handle a case of “Premature Optimization Is the Root of All Evil“.
But this is my blog and I was working with a very big api set, and will only get bigger, so (premature) thinking about memory usage and execution time might be a good idea in the long run.

Before I started this I thought that a for loop was faster then a foreach loop. And I usually pick foreach because it’s easier to write and read.

A quick google lands on a stackoverflow question which concludes the opposite. So I started to test a bit.

There is a big difference in my use case here.

I need to remove array items that need to be excluded from the api results. Most examples you will find online are about editing items.

First tests where quite clear, using a foreach was in most configurations faster than for. The test array I create has 10000 items and every 3rth item should be excluded:

<?php
$test_array = array();
for ( $i = 0; $i <= 10000; $i ++ ) {
    $test_array[] = [
        'index'   => $i,
        'include' => ( $i % 3 === 0 ) ? true : false,
    ];
}

The traditional foreach loop

The way I have been filtering arrays for years.
Create a empty array and only put in the elements the need to be included.

<?php
$filtered_array = [];
foreach ( $test_array as $item ) {
    if ( true === $item['include'] ) {
        $filtered_array[] = $item;
    }
}
return $filtered_array;

Immediately delete item

Don’t use a between array, just unset the item in the parent array

<?php
foreach ( $test_array as $key => $item ) {
    if ( true === $item['include'] ) {
        unset( $test_array[ $key ] );
    }
}
return $test_array;

Traditional pass by reference

The same as before, but the $item is passed by reference.
This is the big difference, since we are not editing the $item we want to remove it from the parent array

<?php
$filtered_array = [];
foreach ( $test_array as &$item ) {
    if ( true === $item['include'] ) {
        $filtered_array[] = $item;
    }
}
return $filtered_array;

Immediately delete pass by reference

Again the same and again passed by reference

<?php
foreach ( $test_array as $key => &$item ) {
    if ( true === $item['include'] ) {
        unset( $test_array[ $key ] );
    }
}
return $test_array;

The for loop

And last the for loop I was wondering about

<?php
$length = count( $test_array );
for ( $i = 0; $i < $length; $i ++ ) {
    if ( false === $test_array[ $i ]['include'] ) {
        unset( $test_array[ $i ] );
    }
}
return $test_array;

the results

I ran each of these loops 5000 times and measured the total time that took.
This was to insure the time between results was big enough to exclude the randomness (at least enough)
The test code I ran

Speed of Loops
  1. 5.6122910976414sec Loop: foreach traditional
  2. 6.0467801094055sec Loop: foreach unset key
  3. 7.7878839969635sec Loop: foreach traditional pass by reference
  4. 7.0686309337616sec Loop: foreach unset key pass by reference
  5. 8.6388339996338sec Loop: for

The traditional foreach I’ve been using for years turned out to be the fastest.
Research hours well spend ?

Bonus edit array item

As said before most examples use editing a array.
So I also ran that scenario. Test code
I upped the loops from 5000 runs to 7500 because the difference was so small.
And still it’s close.

  1. 15.363855123523sec Loop: foreach traditional
  2. 10.987272024155sec Loop: foreach foreach edit array directly
  3. 11.358484983444sec Loop: foreach traditional by reference
  4. 14.363346099854sec Loop: for

Here the traditional is the slowest. Reference is a lot faster as most articles claim.
But editing the array directly was the fastest.

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

<?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.

WP-cli run command over each subsite

If you use WP-cli command on a multisite it be default will only run on the mainsite.
But often you want to change a setting for all the sites.
In my case I wanted to set the timezone to Amsterdam for the whole network. That’s not hard:

wp option set timezone_string 'Europe/Amsterdam'

On a multisite this is a bit more difficult. But the script below will do the same for each site in a multisite.

wp site list --field=url | xargs -I % sh -c 'printf "SITE: %\n"; wp option set timezone_string 'Europe/Amsterdam' --url=%'

It consists of 3 parts.
First create a list of all site url’s

wp site list --field=url

Secondly we pass that on to xargs.
xargs is a very powerfull tool. One that I hardly understand and should go into deeper one day.
This is the best tutorial I found if you want to start with xargs.

The only thing important now is the -I %. This sets the variable to %.
But the most important thing here is that inside the '***' You can run any command. like normal.

xargs -I % sh -c '***'

Which brings us to the final part.
First print the site url on a line, then do the actual command we want to do on each sub site. As you can see we pass on the --url=% where we set the variable given in xargs.

printf "SITE: %\n"; wp option set timezone_string 'Europe/Amsterdam' --url=%