Jump to content

Dynamically create .ics file for existing pages


gmclelland
 Share

Recommended Posts

I'm wondering what is the best way in Processwire to dynamically create .ics files from all my existing event pages?

I would basically like a button on my event pages that says "Add to Calendar"

My pages have a startDate and endDate fields.

Pages have urls like events/name-of-event

I would like it if someone visits events/name-of-event.ics, it would dynamically create the ics file download.

Is something like this possible with Processwire?

Link to comment
Share on other sites

My setup:

->every event is a page

->events are listed only in a overview page

->i change the name of the event pages ot something like my-great-event-title-2016-02-04-id.ics that the page template itself works as a ics file

I've this in my event.php:

<?php
//set correct content-type-header
header('Content-type: text/calendar; charset=utf-8');
header('Content-Disposition: inline; filename='.$page->name.'');

//get the data for the ical file
$homepage = $pages->get('/');
$event_desc = stripslashes(strip_tags($page->event_text));

// get the startdate
$dtstart_date   = $page->getUnformatted("event_start_date");
$dtstart_time   = $page->event_start_time;
$tsstart = strtotime($dtstart_time);

// get the enddate
$dtend_date     = $page->getUnformatted("event_end_date");
$dtend_time     = $page->event_end_time;
$tsend = strtotime($dtend_time);

//set the repetition_type to the recur option value
$event_repetition_type = $page->event_recur;
//set the output for rrule in ical file
$rrule = '';
switch ($event_repetition_type) {
    case '2':
    $rrule = 'RRULE:FREQ=WEEKLY;COUNT=5;';
    break;
    case "3":
    $rrule = 'RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=5;;';
    break;
    case '4':
    $rrule = 'RRULE:FREQ=WEEKLY;INTERVAL=3;COUNT=5;;';
    break;
    case '5':
    $rrule = 'RRULE:FREQ=MONTHLY;COUNT=5;;';
    break;
    case '6':
    $rrule = 'RRULE:FREQ=YEARLY;COUNT=5;';
    break;
    default:
    $rrule = '';
}

//build the .ics data
$ical_data  = 'BEGIN:VCALENDAR';
$ical_data  .= "\r\n";
    $ical_data .= 'VERSION:2.0';
    $ical_data .= 'PRODID:-//'.$homepage->title.'//NONSGML ProcessWire//DE';
    $ical_data .= "\r\n";
    $ical_data .= 'CALSCALE:GREGORIAN';
    $ical_data .= "\r\n";
    $ical_data .= 'METHOD:PUBLISH';
    $ical_data .= "\r\n";
    $ical_data .= 'BEGIN:VEVENT';
    $ical_data .= "\r\n";
        $ical_data .= 'SUMMARY:'.$page->title;
        $ical_data .= "\r\n";
        $ical_data .= 'UID:' . md5(uniqid(mt_rand(), true)) . '@'.$config->httpHost;
        $ical_data .= "\r\n";
        $ical_data .= 'CLASS:PUBLIC';
        $ical_data .= "\r\n";
        $ical_data .= 'STATUS:CONFIRMED';
        $ical_data .= "\r\n";
        if ($rrule) {
            $ical_data .= $rrule;
            $ical_data .= "\r\n";
        }
        $ical_data .= 'DTSTART:'.date('Ymd', $dtstart_date).'T'.date("His", $tsend);
        $ical_data .= "\r\n";
        $ical_data .= 'DTEND:'.date('Ymd', $dtend_date).'T'.date("His", $tsstart);
        $ical_data .= "\r\n";
        $ical_data .= 'DTSTAMP:'.date('Ymd').'T'.date('His');
        $ical_data .= "\r\n";
        $ical_data .= 'CATEGORIES:Landwirtschaft,Maschinenring';
        $ical_data .= "\r\n";
        $ical_data .= 'LOCATION:'.$page->location;
        $ical_data .= "\r\n";
        $ical_data .= 'URL:'.$page->httpUrl;
        $ical_data .= "\r\n";
    $ical_data  .= 'END:VEVENT';
    $ical_data  .= "\r\n";
$ical_data .= 'END:VCALENDAR';

//output
echo $ical_data;

...not the smartest code - could be refactored and opitmized - but it is working for an "save to outlook" link...;)

additional i've a "save in your google calendar" link that works with the google calendar API:

//output Ical Outlook link
echo '<a href="'.$e->url.'"><span class="fa fa-windows" aria-hidden="true"></span> Outlook iCal Link</a><br>';
//output Google Cal link
echo 	'<a target="_blank" href="https://www.google.com/calendar/event?action=TEMPLATE&text='
	.$e->title.
	'&dates='
	.date('Ymd', $e_start).'T'.date("His", $tsstart).'/'.date('Ymd', $e_end).'T'.date("His", $tsend).
	'&location='
	.$e->event_location->title.
	'&details='
	.$e_text_decode.
	'&trp=true"><span class="fa fa-google" aria-hidden="true"></span> Google Calendar Link</a>';

maybe not the best but useful examples...

best regards mr-fan

  • Like 7
Link to comment
Share on other sites

Thanks @mr-fan and @adrian!  @mr-fan your setup is similar, but in my setup there would be a main calendar page and each event posting would have it's own public page.  On that event posting a page there would be a button for "Add to calendar" so that people can add that event to their personal calendars.

So I'm still confused conceptually how this would all work?

Am I supposed to examine the url in my event posting template and check for a .ics extension and do the proper theming?

Link to comment
Share on other sites

@gmclelland -

it should be really simple to achieve an add to calendar, especially with processwire.

1.) check for url segment and if it is valid echo your ical, and exit...

/* HANDLE SEGMENT AND ROUTING
-------------------------------------*/
if($input->urlSegment2) throw new Wire404Exception();

if( strlen($input->urlSegment1) ) {

		$pageName =  substr($input->urlSegment1,-4);
		$pageName = $sanitizer->pageName($pageName);
		$event = $pages->get("name=$pageName");
		if($event->id) {
			
			header('Content-type: text/calendar; charset=utf-8');
			header("Content-Disposition: inline; filename={$input->urlSegment1}");
			
			$options = array(
				// associative array:
				// send vars to your vevent processor here...
			);
				
			wireRenderFile('./inc/vevent.php', $options);
			exit();
		}

	}
}

here is the alternate way

if($input->urlSegment2) throw new Wire404Exception();

if($input->urlSegment1) { // if urlSegment1 render the single camp
	
	$pageName = substr($input->urlSegment1,-4);
	$pageName = $sanitizer->pageName($pageName);
	$event = $page->child("name=$pageName");
	if($event != '') {
		header('Content-type: text/calendar; charset=utf-8');
		header("Content-Disposition: inline; filename={$input->urlSegment1}");
			
		$options = array(
			// associative array:
			// send vars to your vevent processor here...
		);
				
		wireRenderFile('./inc/vevent.php', $options);
		exit();
	} else { // if the event does not exist...
		throw new Wire404Exception();
	}
}

you can generate the VEVENT using a class, function etc..

  • Like 3
Link to comment
Share on other sites

Great introduction to urlSegments. This is a textbook use case. However, some pointers for beginners who might want to copy and paste the code.

You can probably skip the whole $allowedSegs array, imo. I would sanitize the segment like you did, then cut off the extension afterwards (not sure if anything can go wrong doing that string operation on unsanitized input, but it works the same). Then I’d just get a child page of that name. Most importantly, get the child page using $page->child('name=' . $sanitizedSegment), because with $pages->get() you might get an unexpected page of the same name from a different branch of the page tree. If it returns a NullPage, you can still 404.

Also, I would refrain from overwriting $page, just to rule out any potential headaches that might cause :D

  • Like 3
Link to comment
Share on other sites

hey thanks, yeah that was hastily written and untested/not debugged- i just changed the $page var to $event...

wasn't sure about the $allowedSegs also; i usually do that in terms of when i'm expecting a small set of segments, like archive, tags, category, etc;

in this case you have a larger pool of possible segments, but yeah the logic probably needs to be redone here so that you get the name first and then return the 404 later like you said...

Link to comment
Share on other sites

@gmclelland

URL segments are teh way to go like Macruras example shows you have complete control to add routing to the existing event page.

best general information about URL segments for me is here:

https://processwire.com/docs/tutorials/how-to-use-url-segments/

URL segments are really great - i use them very often, too.

regards mr-fan

Link to comment
Share on other sites

what i've been trying out today is just doing it in javascript, using this: https://github.com/nwcell/ics.js/

because then whatever events are viewable in the dom can all be put into a one-click add to calendar on the fly I'm showing events list in a datatable)

what i'll probably do is put each piece of the info into data attributes, like data-title, data-description, data-dtstart, data-dtend

as a test i have this working:

('#download-ics').click(function(e) {
        var cal = ics();
        e.preventDefault();

        $("#events tbody tr").each(function(){
            var title = $(this).find('h3').text();
            var desc = $(this).find('h3').text();
            var loc = $(this).find("dd.location").text();
            var dtstart = $(this).find("dd.dates ul>li").text();
            cal.addEvent(title, desc, loc, dtstart, dtstart);
        });

        javascript:cal.download();
    });

you could also have a single click id for the button next to any individual event and then not do the each

  • Like 3
Link to comment
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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...