How can I delete a page folder programmatically (in plugin)? thoughts on cron jobs welcome

Hello everybody,

in my plugin Simple Events I would like to offer an option to have old events deleted automatically (for my own projects it would certainly be very useful). I have a problem with actually doing this, and also I am wondering how to best implement this.

I’ll start with the problem, as it’s pretty straightforward: how do I best delete a page in PHP, folder and all? I have a list of all pages of type event with a past start date, now what do I do to delete them?

I couldn’t find anything in the docs, so I looked in the Admin plugin code which has a function for that: protected function taskDelete() on line 2184 of admincontroller.php. I went ahead and just tried the bit with Folder::delete($page->path()); substituting $page->path() for my actual path string, and while I did not get any error, nothing got deleted either. I’ll next try copying the whole function and modifying it until it works… :wink: but if somebody can point me to a simpler method, that would be excellent.

The other thing is, when should this deleting actually be done? I’m thinking that this is absolutely a cronjob thing, but setting up a cronjob for Grav on a shared hosting server, which I am sure many people using this plugin are on, can be a daunting task – so far, I haven’t bothered doing it on mine!

So I might offer a button for doing this in the Admin plugin options, or I might do it whenever an Event page gets saved, the latter being super automagical but also the most risky. But in either case, I can’t do it for people who don’t use the Admin plugin.

What do you think? What would be the cleanest solution that still offers reasonable ease of use?

Thank you for your time!

@Netzhexe, The question intrigued me, so I did some playing…

Use-case: Cleanup expired events, without using cronjob and/or Admin plugin.

To minimize the impact of deleting events (pages), it might be best to cleanup events when cache has to be build/refreshed by Grav anyway (e.g. after changing config, or adding/editing/deleting a page).

The event ‘onBuildPagesInitialized’ is being fired by Grav when cache has to be rebuild.

This event is triggered once when pages are going to be reprocessed. This typically happens if the cache has expired or needs refreshing.

Using the above I created the following plugin:

namespace Grav\Plugin;

use Grav\Common\Filesystem\Folder;
use Grav\Common\Plugin;

class CleanupPlugin extends Plugin
    /** @var boolean Keeps track whether events need to be cleared. */
    protected $clearEvents = false;

    public static function getSubscribedEvents()
        return [
            'onPluginsInitialized' => ['onPluginsInitialized', 0],

    public function onPluginsInitialized()
        if ($this->isAdmin()) {
            $this->active = false;

            'onBuildPagesInitialized' => ['onBuildPagesInitialized', 0],
            'onPagesInitialized' => ['onPagesInitialized', 0],

    /** Cleanup up expired events when page collection has been build */
    public function onPagesInitialized()
        if ($this->clearEvents) {
            $pages = $this->grav['pages'];
            $nonPublishedEvents = $pages->all()->ofType('event')->nonpublished();

            foreach($nonPublishedEvents as $event) {
                $now = time();

                // Do not delete page when publish_date is in future.
                if ($event->publishDate() < $now) {

    /** Fired when Grav needs to refresh/build the cache */
    public function onBuildPagesInitialized()
        $this->clearEvents = true;


  • Not sure if this is the best way, but it seems to work…
  • When using a multi-lingual site, Folder::delete() cannot be used.
  • Yes, I use the build-in variables ‘publish_date’ and ‘unpublish_date’. By doing so, I don’t need to check if event(s) should be published or not.
  • The plugin will only cleanup pages when the cache has to be rebuild. If nothing changes in the system, expired events will not be cleared.

Hope this gives you an idea for your own use-case.

Well hello again, thank you so much for this!! Not only would it not have occurred to me to use the cache rebuild, I could also copy most of your code as-is. Feels like I keep getting Christmas presents (even though I don’t celebrate it)!

I’m listing you as a contributor in the next release, since this part of the code already works a treat. I just need to get another thing going before I can release it, but that’ll be the subject of another post… :wink:

@Netzhexe, I’m pleased to hear you are content with the provided sample.

No need to brush up my ego though, it is already as content as it could be… :innocent: