Je ziet het zo vaak in een webshop staan bij producten: Bestel voor een bepaalde tijd en ontvang het morgen. En op zich is dat relevante informatie die een klant aan kan sporen om snel te bestellen. Maar dan moet die tekst natuurlijk wel kloppen.
Wanneer de tijd bijvoorbeeld al verstreken is, dan is het beter om de badge niet te tonen. En wanneer je op zaterdag of zondag geen pakketten meer naar de post brengt, of op laat halen ook niet.
Wat je dus eigenlijk zou willen is een badge met enige ‘intelligentie’. En nu we toch eisen beginnen te stellen, waarom niet gelijk een badge die ook rekening houdt met de erkende feestdagen?
Maar voor we al die intelligentie in gaan bouwen, het eerste wat we hier willen is natuurlijk weten hoe we zo’n bericht in kunnen voegen.
Dat is gelukkig enorm simpel:
<?php
add_action( 'woocommerce_single_product_summary', 'wxp_display_delivery_badge', 6 );
function wxp_display_delivery_badge() {
echo '<div class="woocommerce-message">Bestel voor 18:00 uur en ontvang het morgen</div>';
}
Door deze code wordt net onder de titel van het product geplaatst. Wil je deze ergens anders hebben, dan kan je de derde parameter van add_action verhogen. Hoe hoger het getal, hoe lager de badge op de pagina komt te staan.
Deze code zal je typisch in de functions.php plaatsen.
Wanneer deze tekst te tonen?
Het volgende onderdeel is een manier om deze tekst conditioneel te tonen. Op bepaalde dagen en na bepaalde tijden, wil je dit niet meer zien. Dat wordt wat meer een uitdaging.
Hoewel je deze code ook prima kan plaatsen in de functions.php, is dit eigenlijk een functie die je ‘elders’ zou willen plaatsen.
Het tonen van de badge is iets wat bij je thema hoort, en dus in de functions.php, maar het bepalen wat voor soort dag het is, is eigenlijk een functie die je vaker kan en zal willen gebruiken in mogelijke code uitbreidingen, ongeacht het thema.
En de meest ‘nette’ plaats is jou ‘site specifieke snippet plugin‘.
En deze functie is heel wat complexer. Want we gaan rekenen met feestdagen. Maar niet alle feestdagen vallen ieder jaar op dezelfde dag. Gelukkig zit er wel enige logica in.
Het eerste wat we willen bepalen is of het nu voor of na de uiterste besteltijd is. Maar hierbij willen we natuurlijk rekening houden met de juiste tijdzone, en met de vraag of het wel of geen zomertijd is.
Om dat allemaal goed te doen, moeten we eerst instellen voor welke tijdzone we dit willen bepalen. Nu is dit voor Nederland ‘Europe/Amsterdam’, maar het blijkt uit de bezoeken aan mijn website, dat mijn blog door heel de wereld wordt gelezen, dus voor die mensen die in Curacao, Suriname of waar dan ook wonen, heb ik hier een complete lijst van alle tijdzones.
De eerste functie die we nodig hebben is een controle of het op dit moment al een bepaalde tijd is geweest.
Dat is eenvoudig.
<?php
//Dit pas je aan, wanneer jij niet in Nederland woont
define('WXP_TIME_ZONE','Europe/Amsterdam');
function wxp_nog_op_tijd($tijdstip) {
$timestamp = time();
$dt = new DateTime('now', new DateTimeZone(WXP_TIME_ZONE));
$dt->setTimestamp($timestamp);
return ($tijdstip > intval($dt->format('H')));
}
In dit codefragment zit een aantal zaken die mogelijk compleet nieuw voor je zijn. Alhoewel, wanneer je af en toe hebt gespiekt in de wp-config.php file zullen een paar dingen ook enigszins bekend voorkomen durf ik te wedden.
Met de functie ‘define’ declareren we een zogenaamde ‘constante’. Dat is een waarde die je verder gedurende het lopen van het script niet meer kan wijzigen. Als het ware wordt iedere keer dat PHP ‘WXP_TIME_ZONE’ tegenkomt, deze waarde vervangen door ‘Europe/Amsterdam’, tenzij jij natuurlijk iets anders in jouw code hebt staan. Dit doen we simpelweg, omdat we later deze waarde nog een paar keer nodig hebben, en ik bij een eventuele aanpassing dit maar op één plek wil doen.
Vervolgens wordt de functie ‘wxp_nog_op_tijd’ gedefinieerd. De functie krijgt het uur tot wanneer er besteld kan worden door, en geeft een waarde ’true’ of ‘false’ terug.
Op regel 6 vragen we de huidige tijd op als ‘Unix Timestamp’, dat is het aantal seconden, wat verstreken is sinds 1 januari 1970 0:00 uur.
Dit hebben we straks nodig om de tijd exact in te stellen.
Op regel 7 zie je iets gebeuren, wat ik je volgens mij nog niet eerder heb laten zien. Hier definiëren we objecten. We hebben al vaak genoeg gebruik gemaakt van objecten, iedere keer dat we iets als $post->ID intikten, benaderden we de waarde van de ‘property’ ID van het object $post, maar nu maken we er voor de eerste keer zelf één aan. En het interessante is, dat dat object nog een tweede object nodig heeft, die als parameter wordt meegegeven.
Wat we terugkrijgen is een structuur om ‘dingen’ met datum en tijd te doen. En die hele structuur gebruiken we in regel 8 om de waarde van $timestamp te kunnen gebruiken.
En uiteindelijk… hè hè, in regel 9 gebruiken we een ‘formaat’ om die $timestamp te laten zien. En ‘H’ wil zeggen, dat we de uren willen zien volgens een 24 uurs klok.
Die vergelijken we dan met onze parameter, en als de parameter $tijdstip die aangeeft wat de uiterste tijd is om op tijd te zijn maar groter is, dan de huidige tijd zijn we nog steeds op tijd.
Wanneer we gewoon op alle dagen onze pakjes laten bezorgen, dan zijn we nu klaar.
Wanneer we nu de code invoegen in onze code in de functions.php wordt het hele verhaal
<?php
add_action( 'woocommerce_single_product_summary', 'wxp_display_delivery_badge', 6 );
function wxp_display_delivery_badge() {
if (wxp_nog_op_tijd(18)) {
echo '<div class="woocommerce-message">Bestel voor 18:00 uur en ontvang het morgen</div>';
}
}
Maar we willen nog iets controleren. Namelijk welke dag van de week het is. Want op zaterdag willen we het blok niet tonen, omdat er op zondag niet wordt bezorgd.
De code die we hiervoor nodig hebben plaatsen we ook weer in onze plugin, niet in functions.php.
De code zal je enigszins bekend voorkomen.
<?php
function wxp_dag_vandaag($dagnummer) {
$timestamp = time();
$dt = new DateTime('now', new DateTimeZone(WXP_TIME_ZONE));
$dt->setTimestamp($timestamp);
return ($dagnummer == intval($dt->format('N')));
}
De code lijkt wel heel veel op de eerdere code, nietwaar? En om te voorkomen, dat we iedere keer hetzelfde moeten doen, kunnen we deze een beetje omschrijven. Kijk eens hieronder
<?php
//Dit pas je aan, wanneer jij niet in Nederland woont
define('WXP_TIME_ZONE','Europe/Amsterdam');
function wxp_get_local_time() {
$timestamp = time();
$dt = new DateTime('now', new DateTimeZone(WXP_TIME_ZONE));
$dt->setTimestamp($timestamp);
return $dt;
}
function wxp_nog_op_tijd($tijdstip) {
$dt = wxp_get_local_time();
return ($tijdstip > intval($dt->format('H')));
}
function wxp_dag_vandaag($dagnummer) {
$dt = wxp_get_local_time();
return ($dagnummer == intval($dt->format('N')));
}
Je had waarschijnlijk al begrepen, dat het formaat ‘N’ betekent, dat een dagnummer binnen de week getoond moet worden. Dan is het natuurlijk altijd leuk te weten, hoe de week genummerd wordt, omdat voor sommigen de week op zondag, voor anderen de week op maandag begint.
Hier houden we ons aan de ISO 8601 nummering, waarbij maandag de eerste dag van de week is. Dus om te voorkomen, dat mensen denken dat er op zondag wordt bezorgd, willen we testen tegen de zesde dag van de week.
Voegen we deze functie nu toe aan onze code snippet, dan krijgen we het volgende
<?php
add_action( 'woocommerce_single_product_summary', 'wxp_display_delivery_badge', 6 );
function wxp_display_delivery_badge() {
if (wxp_nog_op_tijd(18) && !wxp_dag_vandaag(6)) {
echo '<div class="woocommerce-message">Bestel voor 18:00 uur en ontvang het morgen</div>';
}
}
Ofwel, wanneer we nog voor 18 uur zijn en het is vandaag geen zaterdag, laat dan de boodschap zien.
Geen bezorging tijdens de feestdagen
Maar hoe zit dan dan met Kerstmis, Pasen, Pinksteren, Hemelvaartsdag, Koningsdag?
Ook dat is geen probleem.
Pasen, Pinksteren en Hemelvaartsdag vallen altijd om verschillende dagen, maar er zit gelukkig wel enige logica in.
Pasen is te berekenen. En nog veel leuker, PHP heeft een standaardfunctie om Pasen te berekenen. Pinksteren is altijd 50 dagen na Pasen en Hemelvaartsdag 40 dagen na Pasen.
Wanneer we die kennis nu allemaal samenvoegen in één grote functie, dan krijgen we iets als het volgende.
<?php
function wxp_get_holidays($offset = 0, $year = null)
{
if ($year === null)
{
$year = intval(date('Y'));
}
$easterDate = easter_date($year);
$easterDay = date('j', $easterDate);
$easterMonth = date('n', $easterDate);
$easterYear = date('Y', $easterDate);
$holidays = array(
// Vaste data
date('Ymd', mktime(0, 0, 0, 1, 1 + $offset , $year)), // Nieuwjaarsdag
date('Ymd', mktime(0, 0, 0, 4, 27 + $offset , $year)), // Koningsdag
date('Ymd', mktime(0, 0, 0, 5, 5 + $offset , $year)), // Bevrijdingsdag
date('Ymd', mktime(0, 0, 0, 12, 25 + $offset , $year)), // Eerste kerstdag
date('Ymd', mktime(0, 0, 0, 12, 26 + $offset , $year)), // Tweede kerstdag
// Pasen gerelateerde feestdagen
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + $offset, $easterYear)), // Pasen
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + 1 + $offset, $easterYear)), // Tweede paasdag
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + 40 + $offset, $easterYear)), //Hemelvaart
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + 50 + $offset, $easterYear)), //Pinksteren
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + 51 + $offset, $easterYear)) //Tweede pinksterdag,
);
sort($holidays);
return $holidays;
}
Deze functie geeft voor een bepaald jaar alle feestdagen weer. Dit is gebaseerd op de Nederlandse feestdagen, wellicht dat je voor België, Suriname of de Antillen hier nog extra feestdagen aan toe wilt voegen.
Omdat dit best een complexe functie is, en een tikfout al snel tot andere resultaten zou kunnen leiden, is het altijd goed deze functie even te testen.
Zelf gebruik ik graag, als ik even snel iets moet testen buiten de context van WordPress om (want dan gaat het niet werken) de PHP Sandbox van OnlinePHP.io.
Wanneer je bovenstaande code met copy/paste inplakt in OnlinePHP.io en de onderstaande code hieronder plaatst:
print_r(wxp_get_holidays());
en vervolgens op ‘Execute code’ klikt, dan zou je daarna iets als dit te zien moeten krijgen in het ‘Results’ venster:
Array
(
[0] => 20230101
[1] => 20230409
[2] => 20230410
[3] => 20230427
[4] => 20230505
[5] => 20230519
[6] => 20230529
[7] => 20230530
[8] => 20231225
[9] => 20231226
)
Wanneer deze datums kloppen met de datums voor feestdagen in je agenda, dan heb je het goed gedaan.
De extra toegevoegde regel mag je weghalen, deze was alleen om de code even te testen.
Het is een behoorlijk verhaal, maar we komen bijna aan het einde.
We hebben nu een prachtige functie die je alle feestdagen laat zien, maar nog geen functie die deze gegevens ook vergelijkt.
Een dergelijke functie is eigenlijk heel eenvoudig
<?php
function wxp_is_feestdag() {
return in_array(date('Ymd'),wxp_get_holidays());
}
Of met andere woorden, wanneer de dag van vandaag voorkomt in het lijstje met feestdagen, dan krijg je een waarde ’true’ terug, anders ‘false’.
Maar wacht eens even! We moesten niet de feestdagen hebben, want dat zijn de dagen waarop er niet bezorgd wordt. We wilden juist de dag voor de feestdag hebben.
Gelukkig heb ik daar stiekem al rekening mee gehouden. Want in de functie ‘wxp_get_holidays’ zie je een parameter $offset.
En met die parameter $offset kan ik aangeven hoeveel dagen voor of na de feestdag gekozen moet worden.
Dus wxp_get_holidays(-1) geeft de dag voor de feestdagen terug.
Maar dan hebben we nog met iets anders te maken.
De door ons gekozen manier om te kijken wat de feestdagen zijn is eigenlijk best een rekenintensieve functie. Iedere keer dat iemand een product pagina opvraagt, zal er een lijst met alle feestdagen van dit jaar worden gegenereerd, en vervolgens gaat de volgende functie op zoek om te zien of deze feestdag voorkomt in een lijstje met feestdagen.
Daar willen we wat aan doen. Want wanneer we nu weten, dat het geen feestdag is, dan is het bij de volgende paginaopvraag
En gelukkig heeft WordPress een mechanisme om gegevens die weinig veranderen en veel tijd kosten om te berekenen op te slaan. Dit soort gegevens worden ’transients’ genoemd. Wat we heel simpel doen, we slaan dat berekende lijstje met data op. Op deze wijze hoeven we maar 1x per jaar het lijstje opnieuw te berekenen.
We gaan de code dus nogmaals herschrijven, waarbij we ditmaal de feestdagen als transient opslaan.
<?php
function wxp_is_feestdag($offset = 0) {
if (false === ($holiday_days = get_transient("wxp_holiday_offset{$offset}")) {
$year = intval(date('Y'));
$expiry = mktime(23,59,59,12,31,$year) - wxp_get_local_time();
$holiday_days = wxp_get_holidays($offset);
set_transient("wxp_holiday_offset{$offset}",$holyday_days,$expiry);
}
return in_array(date('Ymd'),$holiday_days);
}
Wat we hier dus doen is dat we eerst kijken of de transient al voorkomt. Omdat we de functie met verschillende parameters aan kunnen roepen, moeten we voor ieder resultaat een aparte transient genereren, de lijst met feestdagen is immers anders dan een lijst met de dagen voor een feestdag.
Dat controleren we dus als eerste en wanneer die transient niet wordt gevonden, maken we een nieuwe aan, en laten deze vervallen bij de ingang van het nieuwe jaar (de variabele $expiry is gelijk aan één seconde voor het eerstvolgende nieuwjaar, minus de dag van vandaag. Dat is precies het aantal seconden wat het nog duurt tot oud en nieuw).
En tot die tijd halen we fijn de feestdagen op uit de transients zonder allerlei ingewikkelde berekeningen.
En nu alles samen!
En tenslotte voegen we alles nog een keer samen
<?php
//Dit pas je aan, wanneer jij niet in Nederland woont
define('WXP_TIME_ZONE','Europe/Amsterdam');
function wxp_get_local_time() {
$timestamp = time();
$dt = new DateTime('now', new DateTimeZone(WXP_TIME_ZONE));
$dt->setTimestamp($timestamp);
return $dt;
}
function wxp_nog_op_tijd($tijdstip) {
$dt = wxp_get_local_time();
return ($tijdstip > intval($dt->format('H')));
}
function wxp_dag_vandaag($dagnummer) {
$dt = wxp_get_local_time();
return ($dagnummer == intval($dt->format('N')));
}
function wxp_get_holidays($offset = 0, $year = null)
{
if ($year === null)
{
$year = intval(date('Y'));
}
$easterDate = easter_date($year);
$easterDay = date('j', $easterDate);
$easterMonth = date('n', $easterDate);
$easterYear = date('Y', $easterDate);
$holidays = array(
// Vaste data
date('Ymd', mktime(0, 0, 0, 1, 1 + $offset , $year)), // Nieuwjaarsdag
date('Ymd', mktime(0, 0, 0, 4, 27 + $offset , $year)), // Koningsdag
date('Ymd', mktime(0, 0, 0, 5, 5 + $offset , $year)), // Bevrijdingsdag
date('Ymd', mktime(0, 0, 0, 12, 25 + $offset , $year)), // Eerste kerstdag
date('Ymd', mktime(0, 0, 0, 12, 26 + $offset , $year)), // Tweede kerstdag
// Pasen gerelateerde feestdagen
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + $offset, $easterYear)), // Pasen
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + 1 + $offset, $easterYear)), // Tweede paasdag
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + 40 + $offset, $easterYear)), //Hemelvaart
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + 50 + $offset, $easterYear)), //Pinksteren
date('Ymd', mktime(0, 0, 0, $easterMonth, $easterDay + 51 + $offset, $easterYear)) //Tweede pinksterdag,
);
sort($holidays);
return $holidays;
}
function wxp_is_feestdag($offset = 0) {
if (false === ($holiday_days = get_transient("wxp_holiday_offset{$offset}")) {
$year = intval(date('Y'));
$expiry = mktime(23,59,59,12,31,$year) - wxp_get_local_time();
$holiday_days = wxp_get_holidays($offset);
set_transient("wxp_holiday_offset{$offset}",$holyday_days,$expiry);
}
return in_array(date('Ymd'),$holiday_days);
}
Bovenstaande code plaatsen we dus bij voorkeur in onze site specifieke snippet plugin.
En de code hieronder in de functions.php van ons child thema
<?php
add_action( 'woocommerce_single_product_summary', 'wxp_display_delivery_badge', 6 );
function wxp_display_delivery_badge() {
if (wxp_nog_op_tijd(18) &&
!wxp_dag_vandaag(6)) &&
!wxp_is_feestdag(-1) {
echo '<div class="woocommerce-message">Bestel voor 18:00 uur en ontvang het morgen</div>';
}
}
Belangrijk – Vergeet de cache niet!
Een belangrijk punt om niet te vergeten is periodiek de cache leeg te maken, omdat anders de veranderingen in het wel of niet verschijnen van de badge niet doorgevoerd zullen worden. Dat wil je natuurlijk niet iedere keer handmatig hoeven te doen, dus dat automatiseren we natuurlijk het liefst.
De makkelijkste manier is om in een cron job (dus geen wp-cron job!) het volgende commando te geven.
wp cache flush
Hierbij is het uitgangspunt dat wp-cli op jouw server is geïnstalleerd, dus vraag dat eerst even na bij de beheerder van je website of je hoster.
Een andere manier is om de bestanden gewoon via een command prompt te wissen. Dit is echter zeer riskant wanneer je niet exact weet dat je doet. Lees onderstaande dus eerst goed, voor je ook maar iets onderneemt.
De cache voor je website wordt opgeslagen in een folder onder WordPress met het path wp-content/cache/naam-van-de-cacheprovider/, dus bijvoorbeeld wp-content/cache/cache-enabler/ wanneer je cache-enabler zou gebruiken.
Daarnaast is er nog een pad naar jouw website installatie toe. Dat kan er bijvoorbeeld uitzien als
/var/www/je-gebruikersnaam/websites/naam-van-je-website.nl/public_html/
Dus bijvoorbeeld
/var/www/piet/websites/pietzijnwebsite.nl/public_html/
Voegen we al deze informatie bij elkaar, dan krijgen we als totaal path naar je cache toe
/var/www/piet/websites/pietzijnwebsite.nl/public_html/wp-content/cache/cache-enabler/
Als commando voor jouw cron-job gebruik je dan het commando
rm -rf /var/www/piet/websites/pietzijnwebsite.nl/public_html/wp-conent/cache/cache-enabler/*
Deze cronjob (of de hiervoor genoemde cronjob met WP-CLI) laat je dagelijks draaien rond het tijdstip dat je bestellingen uiterlijk binnen moeten zijn om de volgende dag al te kunnen leveren. Hier moet je zelf rekening mee houden de cronjob rond de verandering van de zomertijd aan te passen, de cronjobs rekenen namelijk altijd met wintertijd.
Een tweede mogelijkheid -wanneer je een cache gebruikt- is een relatief korte caching tijd in te stellen, dus minder dan een uur. Het nadeel hiervan is echter dat een korte caching tijd een zwaardere belasting op je server legt.
Een derde alternatief is om de tijd-, dag- en feestdag bepaling niet in PHP uit te voeren, maar dezelfde functionaliteit in JavaScript te (laten) bouwen. Door dit in JavaScript te doen heb je niets te maken met serverside caching, omdat de uitvoering helemaal aan de kant van de browser plaats zal vinden. Nadeel is echter dat de badge pas getoond (of niet getoond) al worden, wanneer de hele pagina is geladen.
Heb je interesse in een JavaScript variant van de code in dit artikel? Laat het dan weten in de commentaren hieronder en bij voldoende interesse werk ik het verder uit.
Heb je interesse in de JavaScript variant en je hebt het op korte termijn al nodig? Dan werk ik het graag voor je uit op basis van de WordXPression Support Strippenkaart.
Ten slotte
Dit was een behoorlijk lang verhaal nietwaar? En dat voor een relatief kleine toevoeging aan de website. Maar al lezend heb je mogelijk toch een aantal interessante, nieuwe dingen over het programmeren voor WordPress geleerd. Want er zijn hier heel wat dingen de revue gepasseerd, die je in je toekomstige WordPress of WooCommerce verder kan gebruiken.
Het is mogelijk dat deze code niet helemaal toepasbaar is op jouw situatie. Want hoe doe je het met producten met verschillende levertijden? Of wanneer je alleen bij een zekere voorraad de producten de volgende dag aan kan bieden? Dan moet de zichtbaarheid afhankelijk gemaakt worden van verschillende andere factoren. In zo’n geval is het altijd de moeite waard om contact met WordXPression op te nemen voor maatwerk.
Begin je schik te krijgen in het zelf toevoegen van functionaliteiten aan WordPress? Dan is wellicht de cursus WordPress Developer iets voor jou.
Aan de hand van dit soort voorbeelden leer jij hoe je zelf meer uit WordPress kan halen, hoe je zelf plugins en thema’s kan maken en nog veel meer. Tweemaal per jaar begint er zo’n training met maximaal 4 deelnemers per training.
Meld je nu aan, voor de training vol is.