Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simple ICS Generation class for PHP
<?php /*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Furkan Mustafa, 2015.04.06
- Updated 2015.04.09: Limit lines to 70 chars (spec is 75)
- Updated 2015.04.26: duplicate letter fixed by @PGallagher69 (Peter Gallagher)
- Updated 2015.04.26: Outtlook Invite fixed by @PGallagher69 (Peter Gallagher)
- Updated 2015.05.02: Line-limit bug fixed by @waddyvic (Victor Huang)
Adapted from: https://gist.github.com/jakebellacera/635416
Also see: https://www.ietf.org/rfc/rfc5545.txt
Development Sponsored by 77hz KK, Tokyo, http://77hz.jp
Usage:
$cal = new SimpleICS();
// $cal->productString = '-//77hz/iFLYER API//';
$cal->addEvent(function($e) {
$e->startDate = new DateTime("2015-04-06T10:00:00+09:00");
$e->endDate = new DateTime("2015-04-06T18:30:00+09:00");
$e->uri = 'http://url.to/my/event';
$e->location = 'Tokyo, Event Location';
$e->description = 'ICS Entertainment';
$e->summary = 'Lorem ipsum dolor ics amet, lorem ipsum dolor ics amet, lorem ipsum dolor ics amet, lorem ipsum dolor ics amet';
});
header('Content-Type: '.SimpleICS::MIME_TYPE);
if (isset($_GET['download'])) {
header('Content-Disposition: attachment; filename=event.ics');
}
echo $cal->serialize();
*/
class SimpleICS {
use SimpleICS_Util;
const MIME_TYPE = 'text/calendar; charset=utf-8';
var $events = [];
var $productString = '-//hacksw/handcal//NONSGML v1.0//EN';
static $Template = null;
function addEvent($eventOrClosure) {
if (is_object($eventOrClosure) && ($eventOrClosure instanceof Closure)) {
$event = new SimpleICS_Event();
$eventOrClosure($event);
}
$this->events[] = $event;
return $event;
}
function serialize() {
return $this->filter_linelimit($this->render(self::$Template, $this));
}
}
class SimpleICS_Event {
use SimpleICS_Util;
var $uniqueId;
var $startDate;
var $endDate;
var $dateStamp;
var $location;
var $description;
var $uri;
var $summary;
static $Template;
function __construct() {
$this->uniqueId = uniqid();
}
function serialize() {
return $this->render(self::$Template, $this);
}
}
trait SimpleICS_Util {
function filter_linelimit($input, $lineLimit = 70) {
// go through each line and make them shorter.
$output = '';
$line = '';
$pos = 0;
while ($pos < strlen($input)) {
// find the newline
$newLinepos = strpos($input, "\n", $pos + 1);
if (!$newLinepos)
$newLinepos = strlen($input);
$line = substr($input, $pos, $newLinepos - $pos);
if (strlen($line) <= $lineLimit) {
$output .= $line;
} else {
// First line cut-off limit is $lineLimit
$output .= substr($line, 0, $lineLimit);
$line = substr($line, $lineLimit);
// Subsequent line cut-off limit is $lineLimit - 1 due to the leading white space
$output .= "\n " . substr($line, 0, $lineLimit - 1);
while (strlen($line) > $lineLimit - 1){
$line = substr($line, $lineLimit - 1);
$output .= "\n " . substr($line, 0, $lineLimit - 1);
}
}
$pos = $newLinepos;
}
return $output;
}
function filter_calDate($input) {
if (!is_a($input, 'DateTime'))
$input = new DateTime($input);
else
$input = clone $input;
$input->setTimezone(new DateTimeZone('UTC'));
return $input->format('Ymd\THis\Z');
}
function filter_serialize($input) {
if (is_object($input)) {
return $input->serialize();
}
if (is_array($input)) {
$output = '';
array_walk($input, function($item) use (&$output) {
$output .= $this->filter_serialize($item);
});
return trim($output, "\r\n");
}
return $input;
}
function filter_quote($input) {
return quoted_printable_encode($input);
}
function filter_escape($input) {
$input = preg_replace('/([\,;])/','\\\$1', $input);
$input = str_replace("\n", "\\n", $input);
$input = str_replace("\r", "\\r", $input);
return $input;
}
function render($tpl, $scope) {
while (preg_match("/\{\{([^\|\}]+)((?:\|([^\|\}]+))+)?\}\}/", $tpl, $m)) {
$replace = $m[0];
$varname = $m[1];
$filters = isset($m[2]) ? explode('|', trim($m[2], '|')) : [];
$value = $this->fetch_var($scope, $varname);
$self = &$this;
array_walk($filters, function(&$item) use (&$value, $self) {
$item = trim($item, "\t\r\n ");
if (!is_callable([ $self, 'filter_' . $item ]))
throw new Exception('No such filter: ' . $item);
$value = call_user_func_array([ $self, 'filter_' . $item ], [ $value ]);
});
$tpl = str_replace($m[0], $value, $tpl);
}
return $tpl;
}
function fetch_var($scope, $var) {
if (strpos($var, '.')!==false) {
$split = explode('.', $var);
$var = array_shift($split);
$rest = implode('.', $split);
$val = $this->fetch_var($scope, $var);
return $this->fetch_var($val, $rest);
}
if (is_object($scope)) {
$getterMethod = 'get' . ucfirst($var);
if (method_exists($scope, $getterMethod)) {
return $scope->{$getterMethod}();
}
return $scope->{$var};
}
if (is_array($scope))
return $scope[$var];
throw new Exception('A strange scope');
}
}
SimpleICS::$Template = <<<EOT
BEGIN:VCALENDAR
VERSION:2.0
PRODID:{{productString}}
METHOD:PUBLISH
CALSCALE:GREGORIAN
{{events|serialize}}
END:VCALENDAR
EOT;
SimpleICS_Event::$Template = <<<EOT
BEGIN:VEVENT
UID:{{uniqueId}}
DTSTART:{{startDate|calDate}}
DTSTAMP:{{dateStamp|calDate}}
DTEND:{{endDate|calDate}}
LOCATION:{{location|escape}}
DESCRIPTION:{{description|escape}}
URL;VALUE=URI:{{uri|escape}}
SUMMARY:{{summary|escape}}
END:VEVENT
EOT;
@PGallagher69

This comment has been minimized.

PGallagher69 commented Apr 13, 2015

Duplicate Letters caused during splitting on Line 108.... Change line at 108 to;

$line = substr($line, $lineLimit);

To fix.

@PGallagher69

This comment has been minimized.

PGallagher69 commented Apr 13, 2015

Also, adding a new line "METHOD:PUBLISH" after line 193 "PRODID:{{productString}}", allows the invites to open on older versions of Outlook (2003 tested).

@furkanmustafa

This comment has been minimized.

Owner

furkanmustafa commented Apr 26, 2015

@PGallagher69 Thanks! Integrated your fix&addition.

@waddyvic

This comment has been minimized.

waddyvic commented May 1, 2015

Hi furkanmustafa,

I was using your SimpleICS class and found a bug. I've forked your code and fixed it. Please feel free to pull the changes back to your code.

@aaroncrawford

This comment has been minimized.

aaroncrawford commented May 13, 2015

For those having time zone issues - remove the \Z on line ~124 :

return $input->format('Ymd\THis');

\Z will return GMT.

@furkanmustafa

This comment has been minimized.

Owner

furkanmustafa commented May 25, 2015

@waddyvic Thanks, merged your changes!
@aaroncrawford Thanks, I'll try to look for some nice way to make it configurable.

@mikejoyceio

This comment has been minimized.

mikejoyceio commented Jun 12, 2015

Awesome job. Thanks!

@kriscreative

This comment has been minimized.

kriscreative commented Jun 17, 2015

Hi,

I'm trying to use this code to create an event calendar for my website.
An ics file is generated, but none of the info passed is in there (only the default values are).
Simply echoing out the passed info before the addEvent call does output all the correct info.
What am I doing wrong?

Thanks for any help!

Code below:

$cal = new SimpleICS();
// $cal->productString = '-//77hz/iFLYER API//';

// Pull events from database
foreach($pages->find("template=event") as $event){

// Convert event start time that was pulled from database to format expected.
$temp = date("Y-m-d",$event->event_start)."T".date("H:i:sP",$event->event_start);

$cal->addEvent(function($e) {
    $e->startDate = new DateTime($temp);
    $e->endDate = new DateTime($temp);
    $e->location = $event->location;
    $e->description = $event->description;
    $e->summary = $event->summary;
});

header('Content-Type: '.SimpleICS::MIME_TYPE);
if (isset($_GET['download'])) {
    header('Content-Disposition: attachment; filename=event.ics');
}
echo $cal->serialize();

}

@furkanmustafa

This comment has been minimized.

Owner

furkanmustafa commented Jun 28, 2015

@kriscreative sorry for late reply, this thing (gist) doesn't have notifications for some reason.

You need to use use keyword for that to work.

$cal->addEvent(function($e) use ($temp, $event) {
@helmerdavila

This comment has been minimized.

helmerdavila commented Apr 4, 2016

Nice

👍

@nailsnglue

This comment has been minimized.

nailsnglue commented Dec 16, 2016

I would really like to use this code on my website. I'm quite new so I am hoping someone can be kind.

I use a database which only prints to pdf various fields from my database and inside that pdf are appointment details. I want to format the Pdf with a link which includes appointment details which will populate the simpleICS.php function via post method which will then download an ics file for the appointmentee can use.

I presume that the code below goes inside my functions.php and the simpleics.php placed in directory but how do you call that function from the actual page I am send the data to in a url string.

$cal = new SimpleICS();
// $cal->productString = '-//77hz/iFLYER API//';
$cal->addEvent(function($e) {
$e->startDate = new DateTime("2015-04-06T10:00:00+09:00");
$e->endDate = new DateTime("2015-04-06T18:30:00+09:00");
$e->uri = 'http://url.to/my/event';
$e->location = 'Tokyo, Event Location';
$e->description = 'ICS Entertainment';
$e->summary = 'Lorem ipsum dolor ics amet, lorem ipsum dolor ics amet, lorem ipsum dolor ics amet, lorem ipsum dolor ics amet';
});
header('Content-Type: '.SimpleICS::MIME_TYPE);
if (isset($_GET['download'])) {
header('Content-Disposition: attachment; filename=event.ics');
}
echo $cal->serialize();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment