MODX Snippet Development Part 5

Modifying our snippet to let the user set the time interval in the snippet tag.

By Bob Ray  |  March 15, 2024  |  6 min read
MODX Snippet Development Part 5

In the last article, we cleaned up our code, improved the design, and made the Snippet more flexible by adding properties to the Snippet tag. In this article we'll look at how to make the specification of the time interval more user-friendly.

Starting Point

For reference here's where we left off in the last article:

/* NewResource Snippet */

/* Default return value */
$output = '';

/* Return value for new resources that are published */
$newOutput = $modx->getOption('showNew', $scriptProperties,
    '<span class="new_resource">NEW!</span>', true);

/* Set $interval as the number of seconds in two weeks */
$interval = $modx->getOption('interval', $scriptProperties, 1209600, true);

/* See if the resource is published */
$published = $modx->resource->get('published');

/* See if publication date is within the last two weeks */
if ($published) {
    /* Get the current time as a timestamp */
    $currentTime = time();

    /* Get the publishedon date and
       convert it to a unix timestamp */
    $ptime = strtotime($modx->resource->get('publishedon'));

    /* Get the resource's age in seconds */
    $age = $currentTime - $ptime;

    if ($age < $interval) {
        /* Yes, it's recent -- Set the return value */
            $output = $newOutput;
        }
    }
}

return $output;

Room for Improvement

Forcing the user to find a conversion utility and enter the time interval in seconds seems a bit unfriendly. What if we could let the user specify the interval in simpler units like days, weeks, and months? We could write our own natural language parser, but fortunately we don't have to because the PHP strtotime() method will do it for us.

In order to use natural language settings, we'll need to redesign the Snippet slightly. We'll also need to add error handling in case the user sends something that can't be parsed by strtotime().

In this version, we'll allow the user to put any combination of time units into the &interval property. The only restrictions are that any numbers must be expressed as digits (e.g., 7, not "seven"), and if you use multiple units, you need to connect them with a plus or minus sign. The fourth example below sets an interval of 8 days (1 week - 1 days). Examples:

&interval = `1 month`
&interval = `6 hours`
&interval = `2 weeks`
&interval = `1 week - 1 day`
&interval = `1 week + day`
&interval = `1 year`

Now the Snippet tag will look like something like this:

[[!NewResource? &showNew=`NEW!` &interval=`2 weeks`]]

New Code

/* NewResource Snippet */

/* Default return value */
$output = '';

/* Return value for new resources that are published */
$newOutput = $modx->getOption('showNew', $scriptProperties,
    '<span class="new_resource">NEW!</span>', true);

/* Set default $interval as the number of seconds in two weeks */
$interval = $modx->getOption('interval', $scriptProperties, '2 weeks', true);

$cutoffTime = strtotime('now - ' . $interval);

if ($cutoffTime === false) {
    $output = '[NewResource] Invalid interval';
}

/* See if the resource is published */
$published = $modx->resource->get('published');

/* See if publication date is within the last two weeks */
if ($published && $cutoffTime) {
    /* Get the current time as a timestamp */
    $currentTime = time();

    /* Get the publishedon date and
       convert it to a unix timestamp */
    $ptime = strtotime($modx->resource->get('publishedon'));

    if ($ptime > $cutoffTime) {
        /* Yes, it's recent -- Set the return value */
            $output = $newOutput;
        }
    }
}

return $output;

We've added a new variable $cutoffTime, which is the timestamp of (now - interval). As a result, we no longer need to calculate the document's age. We can compare the publishedon timestamp ($ptime) with the $cutoffTime directly. We've made our Snippet much more user-friendly without significantly increasing the amount of code or the processing time.

Notice that we've changed the default value of &interval to '2 weeks' so it will have a useful value if the user doesn't send the property. We're also testing the return value of strtotime(), which will always return false if it can't successfully parse the string you send to it. We test the return value with the === operator. It's not really important here (since no successful parse is going to return an empty value), but it's a good habit to use === when testing the return value of a function that returns false on failure. If you use ==, return values that are empty or 0 will be interpreted as failures when they may not be. With ===, the test will only react when the value is a boolean false.

If the strtotime() return value is false, we set the output to an error message, but we need to add some code because the code below that will change the value if the resource was published recently. We've changed the if statement to:

if ($published && $cutoffTime) {

That way, if $cutoffTime is false (due to the failure of strtotime()), the checks for publication and age will be skipped and the $output variable will retain the error message.

Responding to Errors

In the code above, we've chosen to return information immediately about the invalid interval value. The error message will show on the screen where the 'New' message would have gone and hopefully, the user would fix it immediately. Notice that we've prefixed the error message with '[NewResource]', the name of our Snippet. This is a good practice because error messages don't always show up right away, and it can be really helpful to know which Snippet has the error condition.

In some situations (and you could argue that this is one of them), you don't want to disturb the site visitor with error messages. In that case, you'd want to write the error message to the MODX error log. It's simple enough to do that with code like this:

$msg = '[NewResource] Invalid interval';
$modx->log(modX::LOG_LEVEL_ERROR, $msg);
$cutoffTime = strtotime('2 weeks');

We've added a third line here to set the cutoff time to our default. The Snippet will still work, but it will use '2 weeks' for the cutoff time. The user will find out that the value sent in the tag isn't working when the error shows up in the Error log.

In the log line above, everything is standard up to the comma. Virtually all error messages will look like this one up to that comma. The first argument is a constant belonging to the MODX class that tells MODX the level of the error, but if you want to write to the error log and continue, it will always be modx::LOG_LEVEL_ERROR. The second argument is simply the message you want written to the log. It can be as long or as short as you want. For example, you can do something like this if you want to see what's in an array at a certain point in your code:

$msg = print_r($myArray, true);
$modx->log(modX::LOG_LEVEL_ERROR, $msg);

Sending true as the second argument to print_r() above tells it you want the results returned as a string rather than echoed to the screen.

Coming Up

In this article, we looked at how let the user set the time interval using friendlier units and display an error message if something goes wrong. In the next one, we'll see how to use placeholders to give users more flexibility in how the Snippet is used.


Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.