Jump to content
Mr. NiceGuy

Create "virtual pages" for repeating/recurring events in an event calendar

Recommended Posts

I use the following code to make instances of an event with the template single-event to make it recurring. Recurring events have the field recurring checked and in the intervall I let the user specify a time range (as timestamp) until the next event. 

What I now want is to display the original event and create 5 instances (each one intervall step further in the future). 

What I now have: 

            	$today=time();
            	$results = $pages->find("template=single-event,dateEnd>$today, recurring=0, limit=25");
            	$recurring = $pages->find("template=single-event,recurring=1, limit=25");
            	foreach ($recurring as $k) {
            		for ($i=0; $i < 5; $i++) { 
            		$start=$k->dateStart+$i*($k->intervall-$k->dateStart); 
            		$end=$k->dateEnd+$i*($k->intervall-$k->dateEnd);
            		$k->set("dateStart", $start);
            		$k->set("dateEnd", $end);
            		$results->add($k);
            		}
            		

            	}

            	
            	$results->sort("dateStart");

What I get is only the second item i want to have (dateStart+intervall*1). How do I get the other ones with $i=0 and $i>1?  

I assume this doesn't work because the ids of the pages I add to the calendar are the same. Am I right?

Share this post


Link to post
Share on other sites

I needed to calculate recurrent dates for a project and at that time discovered that there is a PHP DatePeriod class for such type of calculations. It was not so easy for me to get my head around this but I finally came up with a function that is calculating recurrences from start date, end date and an interval. Not the same as your scenario, but maybe it can help to get you started.

// DatePeriod calculation taken from http://php.net/manual/de/class.dateperiod.php#109846
function getRecurrences($startTime,$endTime,$interval) {
    $format = 'Y-m-d H:i';

    $start = new DateTime();
    $end = new DateTime();
    $start->setTimestamp($startTime);
    // if interval 0 we calculate weekdays and set start = end;
    $end = ($interval == 0) ? $end->setTimestamp($startTime) : $end->setTimestamp($endTime);

    // if start and end time are the same, we calculate for weekdays and set endtime to 3 month from starttime
    $end = ($start == $end) ? $end->modify('+90 days') : $end->modify( '+1 minute' );
    // set duration to move on in 1 day steps, if we are calculating timestamps for weekdays
    $duration = ($interval != 0) ? 'PT' . $interval . 'M' : "P1D";
    $interval = new DateInterval($duration);
    
    $daterange = new DatePeriod($start, $interval ,$end);

    return $daterange;
}

EDIT: After reading carefully through your post again, I am wondering about your interval.

You say you let the user define an interval as timestamp. But a timestamp typically is one point in time. So I am wondering how your timestamp looks like for describing a time range? Can you give an example?

If you are using something like "+1 day", "+1 week" as an interval, you could use strtotime for your calculations, like

$new_timestamp = strtotime('+1 week', time()); // + 1 week from today
  • Like 3

Share this post


Link to post
Share on other sites

Thanks for the code examples gebeer, unfortunately they don't contribute to solving my problem. 

The +1week example is nice and elegant. Didn't know that one.

You are right, my field intervall returns indeed a timestamp of a time point. The input field for the intervall asks the user to enter the next date when the event should occur. Then I calculate the time difference between the two events. 

I want to implement it like this because I think that this is the simplest solution to the probem. Probably not the most elegant one though ;)

Maybe I should have made that clearer. 

However your post doesn't solve my problem that I can't add multiple instances of the same page with different start/end dates to the same page array. 

Any ideas how can I achieve this?

Share this post


Link to post
Share on other sites

Well I think what your issue is that when you add a page to a page array there will only every be one of that item. You can save the page with your changes but going from your code it looks like you just want to create events from a base event. For this you could look up how to create a new page of the template type that you want or an easier way...just use a php array with the values that you want that are specific for your template. I am sure that others will jump on with real native processwire solutions but without a serious look through the api docs I wouldn't know (that isnt saying much though as I am no expert).

Share this post


Link to post
Share on other sites

I'm not sure if I completely understand the goal here. Should those instances be kept and stored, or does "virtual" mean that they only exist for the purpose of rendering an output and be discarded afterwards? If it's the first, creating copies using $pages->clone() would probably be the way to go.

Share this post


Link to post
Share on other sites

Thanks bitpoet. Must have overlooked the clone function. And yes  you understood it correctly that I wanted to create those virtual instance only on rendering. 

In the end I however decided to create real pages kind of like MuchDev suggested via module by hooking into the save function of the single-event pages. 

The admin for the single-event template now looks like this: 

https://monosnap.com/file/t187kaF2xI7WxF79blA3D982uMT1ko

The code for the module is as follows. Adapt it for your needs ;)

<?php 
class recurringEvents extends WireData implements Module {
  public static function getModuleInfo() {
    return array(
      'title' => 'Recurring Events',
      'summary' => 'Adds a function to create recurring events and add pages to Processwire when a checkbox is checked on page save',
      'version' => 1,
      'autoload' => true,
    );
  }
  public function init() {
    $this->pages->addHookBefore('save', $this, 'hookSave'); 
  }
  public function hookSave($event) {    
    $page = $event->arguments[0];
      # check if recurring is checked on the page beeing saved.
      if ($page->recurring==1) {
                $intervallString=$page->intervall->title;
                  for ($i=1; $i <= $page->n_repeats; $i++) { 
                    // create new page
                      $k= new Page();
                      $k->template = 'single-event'; // set template
                      $k->parent = wire('pages')->get('/events/'); // set the parent 
                      $intervall='+'.$i.' '.$intervallString; // construct the $intervall selector for strtotimestamp +1 week or +1day, increase with iteration by 1
                      $k->setOutputFormatting(false);
                      // Copy page fields from current page to newly created one
                      $k->title=$page->title;
                      $k->organisation=$page->organisation;
                      $k->type=$page->type;
                      $k->location->address=$page->location->address; 
                      $k->body=$page->body;
                      $k->recurring=0;
                      $k->dateStart= strtotime($intervall, $page->dateStart);
                      $k->dateEnd = strtotime($intervall, $page->dateEnd);
                      $k->save();
                  }
          $this->message("Sucessfully created ".($i-1)." copies of recurring event {$k->title}");  
          // on the current page set recurring to 0 again, no page->save() needed because the page will be saved after the hook automatically
          $page->recurring=0;
      }
  }
  
}?>
  • Like 4

Share this post


Link to post
Share on other sites

The above code from Mr. NiceGuy works quite well, but I have a multilanguage site so I need that the childpages have to be created in every language. At the moment they are only created for my default language.

The problem is here:

post-2257-0-27446500-1452097162_thumb.pn

The alias is the same as in the default language and the "active" checkbox is not checked.

How can I create childpages for all languages in this case?

Best regards

Share this post


Link to post
Share on other sites

I have inserted the following code before the $k->save(); command:

                      foreach($this->languages as $lang){                                              
                       if($lang->isDefault()) continue;
                       $k->set("status$lang", 1);
                       $pageName = $page->title->getLanguageValue($lang);
                       $k->set("name$lang", $pageName);
                      }

Now the multilanguage checkbox is activated and the multilanguage page names are in the correct language. Unfortunately the number of the page at the end of the language page name is different between German and English.

The same childpage have the following page name for the URL:

German: termine-im-februar-2

English: events-in-february-1

As you can see the number at the end is always one step lower in English than in German. It is not a really big problem, but if anyone has an explanation or a solution for this behaviour it would be great to post it here.

Share this post


Link to post
Share on other sites

Are there old instances of the German-only pages present in the trash folder? The number at the end gets incremented if a page with the exact same name (including the number) is already present. If you start out with completely fresh page names and no "zombies" in the trash folder, the numbers should match up for new pages.

Share this post


Link to post
Share on other sites

This was also my first thought, but this is not the case. I also added a complete new title and path name I never used before on that site but it doesnt matter. The number problem is still there. It is a little bit spooky. ???

Here are the screenshots where you can see the path names:

German:

post-2257-0-21093700-1452281848_thumb.pn

English:

post-2257-0-38184400-1452281870_thumb.pn

Share this post


Link to post
Share on other sites

Need calendar functionality for a few of my sites and it was doing my head in. After much research, trial and error, decided to go back to the KISS principle (Keep It Simple Sweetie).

  • PW handles dates well so can just be fields in templates
  • Selectors allow you to define start and end end dates easily
  • Want each event page to be a child page of the Calendar page and editable in its own right
  • Only issue was recurring events and this post has given me ideas

Am going to adapt Mr NiceGuys code to suit.

Happy camper!

  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...