One year ago I established a connection between some blog posts here to my portal site via the WordPress REST API, a cron job, and some php-scripts.

This weekend I revisited the code, since there was one detail that kind of bugged me: The REST API treats "categories" as an OR condition; but for my use case I wanted an AND condition. I only want to show posts that have a combination of categories, like "english" AND "webdesign", but the API returns all posts having one of the two categories.

I have updated my portal site to the new Kirby 3 version recently, and that needed little tweaks here and there, and so I had a look at the blog teasers code. After reading the WordPress REST API documentation (again), I think there is no way to get this AND condition I wanted, so I had to refactor the code on the other end.

I decided to reduce my initial call to the API to return the "english" posts first (since this is what really bothered me; having german posts showing on my english portal site), and then filtering those for the other categories like "design", "web", etc in the php code.

Originally I had placed my desired limit for the posts returned as a parameter in the API call. Currently I have no really smart system to deal with the fact that when I query "english" posts I have now way to know how many of them also have the other categories, and thus will be displayed, and how many will be left out. So I doubled the limit for the API call, hoping that the filtering process afterwards will "leave" enough posts to display with my orginal limit.

Another thing that somehow felt wrong was the way I queried the API: Since I didn't want to poll my WordPress blog each time my portal site was visited, I wrote a cronjob that queried the API every hour and stored the response as a json file in the Kirby content directory. My blog teaser controller then "worked" with this file only.

It turns out that Kirby has a nice caching system, so by using this, I now have a cleaner code and no need for the cronjob, which is nice, since my new provider has a rather complicated system to get cronjobs working.

To create a custom cache, only one line in the site config is needed:

    return [
        '' => true

This is my new controller code:

return function( $kirby ) {
    // cache it
    $blogCache = $kirby->cache('blog');
    $blogData  = $blogCache->get('blogData');
    // there's nothing in the cache, so let's fetch it
    if ($blogData === null) {
        $blogData = Remote::get('');
        $blogData = $blogData->content;
        $blogCache->set('blogData', $blogData, 60);
    return [
        'posts' => $blogData

I had some rounds of try/error, the $blogData Remote:get() returned the data expected and populated the display in the frontend, but the cache file was always empty, because at first I tried to directly use the returned object for the cache, but of course the cache file needs a stringified version of that. Hence the $blogData->content here.

In the snippet file I now have the additional filtering for the desired categories.
Maybe this should also go inside the controller, but since I have to loop over the posts anyway, I find it easier to "filter out" here.

$posts = ( isset($posts) ) ? json_decode($posts) : array();
if ( count($posts) > 0 ) : ?>
    <section class="blog-posts">
        <h2 class="vh">Latest posts from my blog</h2>
            <div class="teasers">
                < ?php
                    $limit = 250;
                    $cats  = array(c2,c3,c4,c5);
                    $i     = 0;
                    $posts = json_decode(json_encode($posts));
                    foreach ( $posts as $post ) {
                        $display = 0;
                        foreach ( $cats as $cat ) {
                            if( in_array( $cat, $post->categories ) ){
                        if ( $display > 0 ) {
                            if( $i == 6 ){
                            $title = $post->title->rendered;
                            $link = $post->link;
                            $content = str::excerpt(preg_replace('/\/\*(.*)\*\//','',$post->excerpt->rendered), $limit);
                            $footer = '<p class="post__footer" aria-role="hidden">(<a href="' . $link . '">read on</a>)</p>';
                            if( (strlen($post->content->rendered) < $limit) && in_array($post->format,array('video')) ){
                                $content = $post->content->rendered;
                                $footer = '';
                        <article class="post">
                            <h3 class="post__heading"><a href="<?= $link ?>">< ?= $title ?></a></h3>
                            <div class="post__content">
                                < ?= $content ?>
                            < ?= $footer ?>
                        </article><!-- .post -->
                 < ?php
		</div><!-- .teasers -->
	</section><!-- .posts -->
< ?php endif; ?>

And that's it.

Screenshot portal site with the blog teasers

Now I have only english post teasers from the relevant categories for my portal site, and, as before, the API is only queried once in an hour - but now only if the portal page is visited, which is an improvement to the "dumb" cronjob, that polled anyway.
Further improvement could be to have a look at the timestamp of the cache and query only newer posts, but this also needs to have the already queried info stored more permanently. My current solution has the advantage that changes on older posts are reflected on the portal site as well.