-
-
Save amattu2/e2d3ee4a122052210ee9b56de6620a09 to your computer and use it in GitHub Desktop.
A simple ICS (iCalendar) event generator class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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) | |
Alec M., 2022.02.05 | |
- Updated 2022.02.05: General readability improvements, cleanup, and add namespace | |
Adapted from: https://gist.github.com/jakebellacera/635416 | |
Also see: https://www.ietf.org/rfc/rfc5545.txt | |
*/ | |
namespace amattu; | |
/** | |
* ICS class | |
*/ | |
class SimpleICS { | |
use SimpleICS_Util; | |
/** | |
* ICS file mime type | |
* | |
* @var string | |
*/ | |
public const MIME_TYPE = 'text/calendar; charset=utf-8'; | |
/** | |
* ICS event ID prefix | |
* | |
* NOTE: | |
* (1) This can be a empty string | |
* | |
* @var string | |
*/ | |
public const EVENT_PREFIX = 'SICS'; | |
/** | |
* An array of events in the ICS file | |
* | |
* @var array | |
*/ | |
protected $events = []; | |
/** | |
* A string containing the ICS file generator string | |
* | |
* @var string | |
*/ | |
protected $productString = '-//amattu/SimpleICS//NONSGML v1.0//EN'; | |
/** | |
* A string containing the ICS file template | |
* | |
* @var string | |
*/ | |
public static $Template = null; | |
/** | |
* Class Constructor | |
* | |
* @param string $productString | |
*/ | |
public function __construct($productString = null) | |
{ | |
if ($productString) { | |
$this->productString = $productString; | |
} | |
} | |
/** | |
* Add a new event to the ICS file | |
* | |
* @param array|SimpleICS_Event $eventOrClosure | |
* @return SimpleICS_Event | |
*/ | |
public function addEvent($eventOrClosure) : SimpleICS_Event | |
{ | |
// Instantiate ICS event class | |
if (is_object($eventOrClosure) && ($eventOrClosure instanceof \Closure)) { | |
$event = new SimpleICS_Event(); | |
$eventOrClosure($event); | |
} | |
// Push Event to Array | |
$this->events[] = $event; | |
return $event; | |
} | |
/** | |
* Turn the ICS event array into a string | |
* | |
* @return string | |
*/ | |
public function serialize() : string | |
{ | |
return $this->filter_linelimit($this->render(self::$Template, $this)); | |
} | |
} | |
/** | |
* ICS event class | |
*/ | |
class SimpleICS_Event { | |
use SimpleICS_Util; | |
/** | |
* Event ID | |
* | |
* @var string | |
*/ | |
public $uniqueId; | |
/** | |
* Event Start DateTime | |
* | |
* @var \DateTime | |
*/ | |
public $startDate; | |
/** | |
* Event End DateTime | |
* | |
* @var \DateTime | |
*/ | |
public $endDate; | |
/** | |
* Event TimeStamp | |
* | |
* @var \DateTime | |
*/ | |
public $dateStamp; | |
/** | |
* Event Location | |
* | |
* @var string | |
*/ | |
public $location; | |
/** | |
* Event Description | |
* | |
* @var string | |
*/ | |
public $description; | |
/** | |
* Event URL/URI | |
* | |
* @var string | |
*/ | |
public $uri; | |
/** | |
* Event Summary (Title) | |
* | |
* @var string | |
*/ | |
public $summary; | |
/** | |
* Event ICS Template | |
* | |
* @var string | |
*/ | |
public static $Template; | |
/** | |
* Class Constructor | |
*/ | |
public function __construct() | |
{ | |
$this->uniqueId = uniqid(SimpleICS::EVENT_PREFIX); | |
} | |
/** | |
* Turn the ICS event into a string | |
* | |
* @return string | |
*/ | |
public function serialize() : string | |
{ | |
return $this->render(self::$Template, $this); | |
} | |
} | |
/** | |
* SimpleICS Utility Trait | |
*/ | |
trait SimpleICS_Util { | |
/** | |
* Shorten each input line to specified limit | |
* | |
* @param string $input | |
* @param ?integer $lineLimit | |
* @return string $output | |
*/ | |
function filter_linelimit($input, $lineLimit = 70) | |
{ | |
// Variables | |
$output = ''; | |
$line = ''; | |
$pos = 0; | |
// Iterate over string | |
while ($pos < strlen($input)) { | |
// Find newlines | |
$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; | |
} | |
/** | |
* Format the event date | |
* | |
* @param \DateTime|string $input | |
* @return string $output formatted date | |
*/ | |
function filter_calDate($input) : string | |
{ | |
if (!is_a($input, 'DateTime')) { | |
$input = new \DateTime($input); | |
} else { | |
$input = clone $input; | |
} | |
// Format and return | |
$input->setTimezone(new \DateTimeZone('UTC')); | |
return $input->format('Ymd\THis\Z'); | |
} | |
/** | |
* Convert various input types to a string | |
* | |
* @param string $input | |
* @return string $output | |
*/ | |
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; | |
} | |
/** | |
* Escape quotes in a string | |
* | |
* @param string $input | |
* @return string $output | |
*/ | |
function filter_quote($input) | |
{ | |
return quoted_printable_encode($input); | |
} | |
/** | |
* Escape content from a string | |
* | |
* @param string $input | |
* @return string $output | |
*/ | |
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'); | |
} | |
} | |
/** | |
* ICS Content Template | |
* | |
* @var string | |
*/ | |
SimpleICS::$Template = <<<EOT | |
BEGIN:VCALENDAR | |
VERSION:2.0 | |
PRODID:{{productString}} | |
METHOD:PUBLISH | |
CALSCALE:GREGORIAN | |
{{events|serialize}} | |
END:VCALENDAR | |
EOT; | |
/** | |
* ICS Event Content Template | |
* | |
* @var string | |
*/ | |
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
* Produced: Sat Feb 05 2022 | |
* Author: Alec M. | |
* GitHub: https://amattu.com/links/github | |
* Copyright: (C) 2022 Alec M. | |
* License: License GNU Affero General Public License v3.0 | |
* | |
* This program is free software: you can redistribute it and/or modify | |
* it under the terms of the GNU Affero 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 Affero General Public License for more details. | |
* | |
* You should have received a copy of the GNU Affero General Public License | |
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |
*/ | |
// Require the ICS class | |
require(__DIR__ . "/SimpleICS.class.php"); | |
// Instantiate the ICS class | |
$cal = new amattu\SimpleICS(); | |
// Add a new event | |
$cal->addEvent(function($e) { | |
$e->startDate = new DateTime(); | |
$e->endDate = (new DateTime())->add(new DateInterval('PT130M')); | |
$e->summary = 'Business Meeting'; | |
$e->description = 'Lorem ipsum dolor ics amet, lorem ipsum dolor ics amet, lorem ipsum dolor ics amet, lorem ipsum dolor ics amet'; | |
$e->uri = 'https://example.com'; | |
$e->location = '1600 Pennsylvania Avenue, N.W., Washington, DC 20500'; | |
}); | |
// Add another event | |
/* | |
$cal->addEvent(function($e) { | |
$e->startDate = new DateTime(); | |
$e->endDate = (new DateTime())->add(new DateInterval('PT130M')); | |
$e->summary = 'Personal Meeting'; | |
$e->description = 'Bla bla bla'; | |
$e->uri = 'https://meeting.com'; | |
$e->location = 'https://zoom.us/j/123456789'; | |
}); | |
*/ | |
// Output the file | |
if (!empty($_GET) && isset($_GET['download'])) { | |
header('Content-Type: ' . amattu\SimpleICS::MIME_TYPE); | |
header('Content-Disposition: inline; filename=event.ics'); | |
} else { | |
echo "<pre>"; | |
} | |
echo $cal->serialize(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment