ProcessWire 3.0.130 core updates

This week we’ll take a detailed look at a few useful new API additions made in the last few weeks, along with examples of each.

Work continued in closing out issue reports this week. We’re down to about 60 of them now, though if we subtract issues that aren’t reproducible, are already marked as resolved (close pending), may be moved to the request repo, or are a discussion (rather than something to fix), then we’re closer to half that number. Over these last few weeks we’ve been working on issue reports, but I’ve also been enhancing the core in small ways as well. This post will cover a few of the useful enhancements that have been made in recent weeks.

New WireMail blacklist setting

When I released ProMailer a few weeks ago, one of the feature requests was to have a blacklist of email addresses that should not be allowed to subscribe or be sent email, ever. As it was explained, there are laws in some parts of the world where laws are pretty strict and you have to be careful to prevent emailing some addresses or entire domains. Given that, I thought it made sense to have this built into the ProcessWire core rather than just in a module. After all, if those email addresses are never supposed to be emailed, you don’t want to be sending them any email, regardless of which module it comes from. For instance, you wouldn’t want LoginRegister sending them a “confirm your account” email, nor would you want FormBuilder sending them an auto-responder… just two examples of many.

With the above in mind, the $config->wireMail setting has been updated to contain a “blacklist” property. This property is an array that contains email addresses or email matching rules. WireMail looks at these rules every time you add an email address $mail->to(''); and refuses any that match blacklist rules.

Email blacklist examples

The blacklist rules are best described by example. Below is how it might appear in your /site/config.php file:

 $config->wireMail('blacklist', [
   // blacklist this email address

   // blacklist all emails ending with

   // blacklist all emails ending with

   // blacklist any email ending with (would include too).

   // blacklist any email at any host on (,, but NOT

   // blacklist any email containing “xyz”. PCRE regex assumed when "/" is used as opening/closing delimiter.

   // another example of using a PCRE regular expression (blocks all "").

Here’s how you can test out your blacklist rules:

$email = '';
$result = $mail->isBlacklistEmail($email, [ 'why' => true ]);
if($result === false) {
  echo "<p>Email address is not blacklisted</p>";
} else {
  echo "<p>Email is blacklisted by rule: $result</p>";

WireMail automatically uses the blacklist rules in 3.0.129 and newer. Third party WireMail modules should also automatically use them as well, unless they have overridden the “to()” method logic. I didn’t find any WireMail modules where this was the case, but just to be safe, you should double check before assuming it works. You can do this by temporarily adding similar rules that match your own email address and then confirming that it works (or rather, doesn’t work—as in, doesn’t email the address).

New $cache->renderFile() method

This is one of my personal favorites in terms of recent additions. If you’ve used the $files->render() method or the function version of it called wireRenderFile(), then you might like the new $cache->renderFile() method. It does the same thing as $files->render() (rendering any PHP file as a template file with all API variables, etc.) except that it caches the output so that it doesn’t have to re-render the same file until your cache expiration rules take effect.

I added this method because I was repeatedly building combinations of $files->render(), $cache->get() and $cache->save(), and have been looking to do it with much less code. I also wanted a solution which would be aware of changes to the source file, and my previous combination of method calls couldn't easily accomplish that.

Unlike caching the result of a $files->render() method yourself, if you make any changes to the file you asked it to render, it automatically expires the cache and re-renders the file, generating the new cache. This makes it very friendly to use, as caching is never likely to interfere with development changes.

This method is very convenient for rendering expensive partials in your site like big navigation lists, or anything that takes some horsepower to render due to loading lots of pages and/or producing lots of resulting markup. I’ve found this method particularly helpful on the current website for caching our primary navbar and offcanvas navigation, as well as category dropdown and lists that appear in the Showcase and Blog.

How to use it

// render primary nav from site/templates/partials/primary-nav.php
// and cache for 3600 seconds (1 hour)
echo $cache->renderFile('partials/primary-nav.php', 3600); 

The file you give it can be a full path to a file, or it can be relative to the current work directory (which would typically be /site/templates/ when on the front-end). Though when in doubt, use the full path. The second argument ($expire) is the expiration time. You can specify any of the following for this argument:

  • Specify one of the WireCache::expire* constants.
  • Specify the future date you want it to expire (as unix timestamp or any strtotime() compatible date format)
  • Provide a Page object to expire when any page using that template is saved.
  • Specify WireCache::expireNever to prevent expiration.
  • Specify WireCache::expireSave to expire when any page or template is saved.
  • Specify selector string matching pages that–when saved–expire the cache.
  • Omit for default value, which is WireCache::expireDaily.

There’s also an optional 3rd argument for an $options array. There are several options here, but the one you might be most likely to use is the “vars” option. This lets you bundle in some variables to send to the file being rendered. They become locally scoped variables just like the existing API variables that are received by the file.

If you are already using $files->render() or wireRenderFile(), then you can also access the cache functionality using either of those existing functions and specifying a “cache” option in the $options argument, where the value is one of the $expire argument options specified above.

New $datetime->elapsedTimeStr() method

You may be familiar with the $datetime->relativeTimeStr() method which has been in the core for quite a long time (and one that I use quite a lot). However, I sometimes have the need to compare two relative times to each other (showing the difference) rather than just one time relative to the current. I’m guessing some of you have had a similar need. That’s where the new $datetime->elapsedTimeStr() method comes into play. It takes two dates/times rather than just one, and gives you the difference in a nicely formatted value .

Like the relativeTimeStr() method, the elapsedTimeStr() includes the ability to adjust the length of the output according to how much abbreviation you want. Depending on what abbreviation type you choose, it uses the same translations as wireRelativeTimeStr(). Though it also supports digital output, which is commonly used for showing stopwatch-style elapsed time.

Example inputs and outputs

Here are some examples of calls and the resulting output below. First, lets define an array of times to test where the keys are the start times and the values are the stop times. We'll use a few different formats just for demonstration purposes:

// start and stop times
$times = [
  // start time => stop time
  "2019-04-10 11:06 AM" => "2019-04-12 3:26 PM",
  "12 April 2019 7:00 AM" => "12 April 2019 1:15 PM",
  "-99 MINUTES" => time(),
  30 => 3600, // can also be a quantity of seconds like this

Now let's call the elapsedTimeStr() method for each of those start/stop times above, and experiment with the $abbreviate argument to demonstrate different outputs:

foreach($times as $start => $stop) {
  $a = [
    $datetime->elapsedTimeStr($start, $stop), // default (verbose)
    $datetime->elapsedTimeStr($start, $stop, true), // abbreviated
    $datetime->elapsedTimeStr($start, $stop, 1), // very-abbreviated
    $datetime->elapsedTimeStr($start, $stop, 0), // digital clock
  echo "<h2>$start => $stop</h2>";
  echo "<ul><li>" . implode("<li>", $a) . "</ul>";

Here's the resulting output:

2019-04-10 11:06 AM => 2019-04-12 3:26 PM

  • 2 days 4 hours 20 minutes
  • 2 days 4 hrs 20 mins
  • 2d 4hr 20m
  • 52:20:00

12 April 2019 7:00 AM => 12 April 2019 1:15 PM

  • 6 hours 15 minutes
  • 6 hrs 15 mins
  • 6hr 15m
  • 06:15:00

-99 MINUTES => 1555081805

  • 1 hour 39 minutes
  • 1 hr 39 mins
  • 1hr 39m
  • 01:39:00

30 => 3600

  • 59 minutes 30 seconds
  • 59 mins 30 secs
  • 59m 30s
  • 00:59:30

Thanks for reading and I hope that you have a great weekend! Be sure to visit the ProcessWire Weekly this weekend for the latest and best ProcessWire news too.


  • Ivan


    • 5 years ago
    • 10

    Does $cache->renderFile() method works with direct output $files->include() ? website hosting upgrades


Quietly and without interruption this week, our whole website moved from a single static server to a load-balanced multi-server environment, giving us even more horsepower and redundancy than before. More 

Latest news

  • ProcessWire Weekly #499
    In the 499th issue of ProcessWire Weekly we'll check out the latest weekly update from Ryan, introduce a new third party module called RockPdf, and more. Read on! / 3 December 2023
  • Using date range fields in ProcessWire
    This week we'll take a detailed look at the newest addition to the ProFields set of modules: the Date Range Fieldtype and Inputfield.
    Blog / 24 November 2023
  • Subscribe to weekly ProcessWire news

“We were really happy to build our new portfolio website on ProcessWire! We wanted something that gave us plenty of control on the back-end, without any bloat on the front end - just a nice, easy to access API for all our content that left us free to design and build however we liked.” —Castus, web design agency in Sheffield, UK