WordPress biedt auteurs standaard de mogelijkheid om de publicatie van berichten in te plannen. Door bij het schrijven van een nieuw bericht de publicatiedatum in de toekomst te zetten zou WordPress dit automatisch voor jou moeten publiceren.

Maar dit is niet feilloos. Wanneer je website te maken heeft met een hoge piek of beperkte resources kan het al eens zijn dat WordPress het bricht niet voor jou heeft kunnen publiceren. Op zich is dit geen ramp, maar het grote probleem is dat WordPress geen tweede poging zal doen. Bij dit bericht zal er in de backoffice de melding “gemist schema” komen, maar daarmee is het probleem uiteraard niet opgelost.

Ik heb een plugin geschreven waardoor WordPress om de 5 minuten berichten met een “gemist schema” alsnog probeert te publiceren. Wie niet al te technisch is aangelegd kan hem hier downloaden, maar nog plezanter is om hem via onderstaande tutorial samen met mij te schrijven.

Stap 1 – Basisstructuur

De uiteindelijke structuur van de plugin zal er als volgt uitzien:

  • tpt-missed-schedule/
    • tpt-missed-schedule.php

Ik gebruik tpt als prefix voor mijn plugins, maar je bent hier uiteraard vrij in. Bovenaan de plugin geef je de meta data op van de plugin:

<?php

/**
 * Plugin Name: Missed schedule
 * Author: Turpoint
 * Author URI: https://turpoint.com
 * Description: Catches missed schedules (auto publishing posts).
 */

Onmiddellijk hieronder maak ik mijn plugin klasse aan. Persoonlijk werk ik graag met klasses waarin ik hooks, filters en andere functionaliteiten binnen dezelfde kleine plugin stop.

namespace Turpoint;

new Missed_Schedule;

class Missed_Schedule
{
    /**
     * Constructor
     */
    public function __construct()
    {
        //
    }
}

Stap 2 – Eigen wp-cron interval definiëren

WP-Cron biedt standaard maar een beperkt aantal intervallen aan waarop we een functie kunnen aanroepen:

  • 1 keer per uur (hourly)
  • 1 keer per dag (daily)
  • 2 keer per dag (twicedaily)

Deze intervallen zijn voor onze case iets te kort. Indien het automatisch publiceren van ingeplande bericht mislukt, zou het toch maximum 5 minuten later opnieuw geprobeerd moeten worden.

Daarom zal ik in de plugin een extra interval definiëren, genaamd five_minutes, dit kan via de filter cron_schedules. Declareer deze filter in de __construct functie van de Missed_Schedule klasse:

// ...

public function __construct()
{
    add_filter('cron_schedules', [$this, 'add_five_minutes_cron_schedule']);
}

 /**
 * Add five minutes cron schedule
 */
public function add_five_minutes_cron_schedule($schedules)
{
    $schedules['five_minutes'] = [
        'interval' => 300,
        'display' => __('Five minutes'),
    ];
    return $schedules;
}

// ...

Stap 3 – Onze eigen WP-Cron actie inplannen

Nu het interval is aangemaakt, kunnen we op dit interval een bepaalde actie inplannen. WP-Cron zal deze actie dan om de 5 minuten aanroepen. Het inplannen van deze actie hoeft maar één keer te gebeuren, dus doen we dit best wanneer de plugin geactiveerd wordt.

// ...

public function __construct()
{
    // ...
    register_activation_hook(__FILE__, [$this, 'schedule_cron_job']);
}

// ...

/**
 * Schedule the cron job
 */
public function schedule_cron_job()
{
    if (wp_next_scheduled('tpt_catch_missed_schedule')) {
        return;
    }
    wp_schedule_event(time(), 'five_minutes', 'tpt_catch_missed_schedule');
}

// ...

Het is van belang om deze actie terug uit de planning te halen wanneer onze plugin niet langer gebruikt wordt. Op zich is dit niet verplicht, maar het is niet nodig dat WordPress acties blijft aanroepen van plugins die niet meer actief zijn op de website.

Net zoals we de actie ingepland hebben via register_activation_hook, kunnen we de actie verwijderen via register_deactivation_hook.

// ...

public function __construct()
{
    // ...
    register_deactivation_hook(__FILE__, [$this, 'unschedule_cron_job']);
}

// ...

/**
 * Unschedule the cron job
 */
public function unschedule_cron_job()
{
    $timestamp = wp_next_scheduled('tpt_catch_missed_schedule');
    wp_unschedule_event($timestamp, 'tpt_catch_missed_schedule');
}

// ...

Stap 4 – Het opvangen van een gemist schema

En dan nu het belangrijkste onderdeel van de plugin. Hier kijken we welke berichten ingepland waren, maar op dit moment nog steeds niet gepubliceerd zijn.

We halen hiervoor de berichten op die als status future hebben, maar waarvan de publicatiedatum tegelijk in het verleden ligt. Dit zijn de berichten met “Schema gemist”.

// ...

/**
 * Constructor
 */
public function __construct()
{
    // ... 

    add_action('tpt_catch_missed_schedule', [$this, 'catch_missed_schedule']);
}

// ...

/**
 * Catch missed schedule
 */
public function catch_missed_schedule()
{
    global $wpdb;
    $now = gmdate('Y-m-d H:i:00');

    $args = [
        'public' => true,
        'exclude_from_search' => false,
        '_builtin' => false
    ];
    $post_types = get_post_types($args, 'names', 'and');
    $str = implode('\',\'',$post_types);

    if ($str) {
        $sql = "Select ID from $wpdb->posts WHERE post_type in ('post','page','$str') AND post_status='future' AND post_date_gmt<'$now'";
    }
    else { 
        $sql = "Select ID from $wpdb->posts WHERE post_type in ('post','page') AND post_status='future' AND post_date_gmt<'$now'";
    }

    $posts = $wpdb->get_results($sql);
    
    if ($posts) {
        foreach ($posts as $post) {
            wp_publish_post($post->ID);
        }
    }
}

// ...

Bovenstaand stukje code doet het volgende:

  • Het haalt de post types van de website op
  • Het stelt een database query samen die de posts ophaalt met als post_status = future, maar waarvan de publicatiedatum in het verleden ligt
  • Het loopt over de posts en publiceert ze één voor één

Eindresultaat

Ter referentie, het volledige pluginbestand tpt-missed-schedule.php ziet er dus als volgt uit:

<?php

/**
 * Plugin Name: Missed schedule
 * Author: Turpoint
 * Author URI: https://turpoint.com
 * Description: Catches missed schedules (auto publishing posts).
 */

namespace Turpoint;

new Missed_Schedule;

class Missed_Schedule
{
    /**
     * Constructor
     */
    public function __construct()
    {
        register_activation_hook(__FILE__, [$this, 'schedule_cron_job']);
        register_deactivation_hook(__FILE__, [$this, 'unschedule_cron_job']);

        add_filter('cron_schedules', [$this, 'add_five_minutes_cron_schedule']);

        add_action('tpt_catch_missed_schedule', [$this, 'catch_missed_schedule']);
    }

    /**
     * Add five minutes cron schedule
     */
    public function add_five_minutes_cron_schedule($schedules)
    {
        $schedules['five_minutes'] = [
            'interval' => 300,
            'display' => __('Five minutes'),
        ];
        return $schedules;
    }

    /**
     * Schedule the cron job
     */
    public function schedule_cron_job()
    {
        if (wp_next_scheduled('tpt_catch_missed_schedule')) {
            return;
        }
        wp_schedule_event(time(), 'five_minutes', 'tpt_catch_missed_schedule');
    }

    /**
     * Unschedule the cron job
     */
    public function unschedule_cron_job()
    {
        $timestamp = wp_next_scheduled('tpt_catch_missed_schedule');
        wp_unschedule_event($timestamp, 'tpt_catch_missed_schedule');
    }

    /**
     * Catch missed schedule
     */
    public function catch_missed_schedule()
    {
        global $wpdb;
		$now = gmdate('Y-m-d H:i:00');
	
    	$args = [
            'public' => true,
	        'exclude_from_search' => false,
    	    '_builtin' => false
        ];
    	$post_types = get_post_types($args, 'names', 'and');
		$str = implode('\',\'',$post_types);

		if ($str) {
			$sql = "Select ID from $wpdb->posts WHERE post_type in ('post','page','$str') AND post_status='future' AND post_date_gmt<'$now'";
		}
		else { 
            $sql = "Select ID from $wpdb->posts WHERE post_type in ('post','page') AND post_status='future' AND post_date_gmt<'$now'";
        }

        $results = $wpdb->get_results($sql);
        
 		if ($results) {
			foreach ($results as $post) {
				wp_publish_post($post->ID);
            }
		}
    }
}