Virtual clone of page in pages collection / repeatable events

Hello Kirby community,
I am using pages to represent events. Now I need to implement weekly repeatable events.

My idea was: a foreach() loop goes through the pages collection and checks each event if it should be repeated and if the final repeat is more than or exactly one week in the future.
If that is true, it appends a copy of the page to the end of the collection with an updated date.
When the foreach loop arrives at the end of the pages collection it will loop over the just created copy, check it again if the next repetition would be earlier than the date where the repetition should stop.
If true, copy it again with a newly changed date.
The whole thing stops when no repetitions are left to copy and the last page in the collection is reached. Then I would sort the collection by date and display it on the front end.

I added two fields, a toggle called repeat_weekly and a date field called repeat_end and tried around with this code:

foreach($events as $entry) {
  if ($entry->repeat_weekly()->isNotEmpty() && $entry->repeat_weekly()->toBool()) {				
    if ($entry->repeat_end()->toDate() >= $entry->date()->toDate()  + 604800) {
      $events = $events->add($entry->clone([
        'date' => $entry->date()->toDate() + 604800
      ]));
    }
  }
}

Of course it’s not working, though I’m not exactly sure why. I suspect I don’t understand exactly how the page clone() method and the pages add() method are working.

Is there any way I can achieve this? I only care about the event being displayed in the front end for each repetition at the right position in the list – they should all link to the original page/event.
Many thanks in advance!

Whilst im not sure why your approach doesnt work, I have a plugin for repeating events. GitHub - HashandSalt/kirby-recurr: Plugin for listing recurring dates

You might find that usefull.

I second plugin that can help if you need to filter out public holidays from those events so they dont happen on say Christmas day or whatever.

2 Likes

Thanks so much @jimbobrjames
Unfortunately, this is an existing website with many events already in there and in the archive, so I’d much prefer a solution that works with my existing implementation. But I will take a look at how it’s implemented in your plugin on Monday.

Well from my debugging efforts I know a bit better, what in my approach doesn’t work – it’s the $events->add($entry->clone()) part. The conditionals evaluate as true, but the pages collection $events has exactly as many entries before and after the code has run, according to $events->count(). And sure enough, no cloned events show up in the front end.

The problem here is that your collection cannot have 2 entries with the same slug, so when you clone the page, it has the exact same props, i.e. also the same slug. So you need to change at least the slug:

$newCollection = new Pages();
foreach ($events as $event) {
  $newCollection->add($event);
  $newCollection->add($event->clone(['slug' => 'new-page']);
}
dump($newCollection);

Ideally, you would give this new page the correct date as well.

1 Like

Thank you! The cloning works now. However, I am unable to set the date one week in the future from the old date. Tried several different things, this is my latest attempt:

$events->add($entry->clone([
  'slug' => $entry->slug() . '-rep',
  'date' => new Kirby\Toolkit\Date($entry->date()->toDate() + 604800),
]));

From my understanding of the documentation Date | Kirby CMS, this should create a new Date one week in the future. Which it does, but the cloned event still has the old date when echoing it in the loop. For some reason the date attribute doesn’t get changed.
Also tried $entry->date(DateInterval::createFromDateString('1 week')) without any luck, this only returns $entry->date() without adding a week. The documentation for all this is not very verbose unfortunately

I also tried adding a new attribute when cloning, to have access to the original page data (I need an image and the original page URL) when displaying the clone, also without success. I tried this:

$events->add($entry->clone([
  'slug' => $entry->slug() . '-rep',
  'original_event' => $entry->original_event()->isNotEmpty() ? $entry->original_event() : $entry,
]));

but I suspect when cloning you can’t add attributes, just modify existing ones?
Any help is much appreciated!

Your fields are part of content, not directly of the page object, so you need the following syntax:

   $newPages->add($note->clone(['slug' => 'new-page', 'content' => [
        'title' => 'Lalalala',
        'date'  => date('Y-m-d', strtotime($event->date()->toDate('Y-m-d') . ' +1 week')),
    ]]));
1 Like

Perfect, thank you! Is there a place in the documentation that talks in detail about how these objects are constructed? I’d love to learn more about this without digging in kirby’s source code, if that can be avoided.

For reference, I ended up with this:

foreach($events as $entry) {
  if ($entry->repeat_weekly()->isNotEmpty() && $entry->repeat_weekly()->toBool()) {				
    if ($entry->repeat_end()->toDate() > $entry->date()->toDate()) {
      $date = strtotime('+1 week', $entry->date()->toDate());
      while ($date <= $entry->repeat_end()->toDate()) {
        $events->add($entry->clone([
          'slug' => $entry->slug() . '-' . $date,
          'content' => [
            'original_event' => $entry->original_event()->isNotEmpty() ? $entry->original_event() : $entry,
            'date' => date('Y-m-d', $date),
          ]
        ]));
        $date = strtotime('+1 week', $date);
      }
    }
  }
}

It goes through all events, and if one needs to be repeated, it will get repeated weekly until the end date is reached. Each repeated event gets an updated slug by appending the repetition’s date, an updated date, and a reference to the original event in the prop ‘original_event’ for referencing the original data in the event listing.

Why do you actually need a collection with all these events? It can get huge, particularly if the end dates are far into the future. I guess, so might make sense to cache them.

I once did something similar for calendar entries, but never created such a collection, but rather filtered the events by date or period.

I use the collection to show the events in the front end. No idea how to cache them – How would I go about doing that?