Skip to content

Instantly share code, notes, and snippets.

@Accudio
Created November 25, 2022 14:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Accudio/24da23ed7399f9298b9319c39b8ab9cc to your computer and use it in GitHub Desktop.
Save Accudio/24da23ed7399f9298b9319c39b8ab9cc to your computer and use it in GitHub Desktop.
Class to generate Calendar URLs and files
<?php
/**
* Utility for adding an event to various calendars
*
* @package SMRC
*/
if (isset($_POST['smrc_add_to_diary'])) {
$event = new SMRC_Diary_Event($_POST);
$event->handle();
}
class SMRC_Diary_Event {
const DT_FORMAT = 'Ymd';
const GOOGLE = 'https://calendar.google.com/calendar/render';
const OUTLOOK = 'https://outlook.live.com/owa/';
const YAHOO = 'https://calendar.yahoo.com/';
protected $user = [];
protected $properties = [];
private $available_properties = [
'title', // all
'start', // all
'end', // all
'description', // all
'location', // all
'timezone', // google and iCal
'url', // iCal
'organizer_name', // google and iCal
'organizer_url' // google and iCal
];
/**
* Object creation
*/
public function __construct($properties)
{
$this->user = $properties;
$this->properties = [
'description' => '',
'location' => '',
'timezone' => 'Europe/London',
'organizer_name' => 'Scottish Motor Racing Club',
'organizer_url' => 'https://smrc.co.uk'
];
$this->set($properties);
$this->addons();
}
public function set($key, $val = false)
{
if (is_array($key)) {
foreach ($key as $k => $v) {
$this->set($k, $v);
}
} else {
if (in_array($key, $this->available_properties)) {
$this->properties[$key] = $this->sanitize($val, $key);
}
}
}
private function addons()
{
if (!isset($this->properties['end'])) {
$this->properties['end'] = $this->properties['start'];
}
$end_plus_one = new DateTime($this->properties['end']);
$end_plus_one->modify('+1 day');
$this->properties['end+1'] = $end_plus_one->format('Ymd');
}
/**
* Object output
*/
public static function form($args, $class_base = 'o-calendarAdd')
{
$class_base = esc_attr($class_base);
$out = '<form target="_blank" method="post" class="' . $class_base . '">';
$out .= ' <input type="hidden" name="smrc_add_to_diary" value="">';
foreach ($args as $key => $val) {
$out .= '<input type="hidden" name="' . esc_attr($key) . '" value="' . esc_attr($val) . '">';
}
$buttons = [
['Google Calendar', 'google', 'google'],
['Apple Calendar', 'apple', 'ics'],
['Outlook.com', 'outlook', 'outlook'],
['Yahoo!', 'yahoo', 'yahoo'],
['Microsoft Office Outlook', 'outlook', 'ics']
];
$out .= ' <div class="' . $class_base . '__list">';
foreach ($buttons as $button) {
$out .= ' <button type="submit" name="' . $button[2] . '" title="' . $button[0] . '" class="' . $class_base . '__button">';
$out .= TCA_Icons::get($button[1], $class_base . '__icon', false);
$out .= ' <span class="sr-only">' . $button[0] . '</span>';
$out .= ' </button>';
}
$out .= ' </div>';
$out .= '</form>';
echo $out;
}
public function handle()
{
if (isset($_POST['google'])) {
header('Location: ' . $this->google());
} else if (isset($_POST['outlook'])) {
header('Location: ' . $this->outlook());
} else if (isset($_POST['outlook'])) {
header('Location: ' . $this->outlook());
} else if (isset($_POST['yahoo'])) {
header('Location: ' . $this->yahoo());
} else if (isset($_POST['ics'])) {
header('Content-Type: text/calendar; charset=utf-8');
header('Content-Disposition: attachment; filename=event.ics');
echo $this->ics();
}
exit;
}
/**
* Generation
*/
public function google()
{
$organizer_url = preg_replace('#^https?://#', '', rtrim($this->properties['organizer_url'],'/'));
$sprop = 'sprop=website:' . $organizer_url . '&sprop=name:' . $this->properties['organizer_name'];
$args = [
'action' => 'TEMPLATE',
'text' => $this->properties['title'],
'dates' => $this->properties['start'] . '/' . $this->properties['end+1'],
'ctz' => $this->properties['timezone'],
'details' => $this->properties['description'],
'location' => $this->properties['location'],
'sprop' => $sprop
];
$query = http_build_query($args, null, '&', PHP_QUERY_RFC3986);
$google = self::GOOGLE . '?' . $query;
return $google;
}
public function outlook()
{
$args = [
'path' => '/calendar/action/compose',
'rru' => 'addevent',
'startdt' => $this->properties['start'],
'enddt' => $this->properties['end'],
'subject' => $this->properties['title'],
'allday' => 'true',
'body' => $this->properties['description'],
'location' => $this->properties['location']
];
$query = http_build_query($args, null, '&', PHP_QUERY_RFC3986);
$outlook = self::OUTLOOK . '?' . $query;
return $outlook;
}
public function yahoo()
{
$args = [
'v' => 60,
'title' => $this->properties['title'],
'st' => $this->properties['start'],
'desc' => $this->properties['description'],
'in_loc' => $this->properties['location'],
'rem1' => '1D'
];
if ($this->properties['start'] === $this->properties['end']) {
$args['dur'] = 'allday';
} else {
$args['et'] = $this->properties['end+1'];
}
$query = http_build_query($args, null, '&', PHP_QUERY_RFC3986);
$yahoo = self::YAHOO . '?' . $query;
return $yahoo;
}
public function ics()
{
// add header
$ics_props = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//SMRC//NONSGML SMRC.CO.UK//EN',
'CALSCALE:GREGORIAN',
'BEGIN:VEVENT'
];
$props = [
'STATUS' => 'CONFIRMED',
'SUMMARY' => $this->ics_escape($this->properties['title']),
'DTSTART;VALUE=DATE' => $this->properties['start'],
'DTEND;VALUE=DATE' => $this->properties['end+1'],
'LOCATION' => $this->ics_escape($this->properties['location']),
'DESCRIPTION' => $this->ics_escape($this->properties['description']),
'URL;VALUE=URI' => $this->properties['url'],
'DTSTAMP' => $this->format_time('now', 'Ymd\THis\Z'),
'UID' => uniqid()
];
$props['X-ALT-DESC;FMTTYPE=text/html'] = $props['DESCRIPTION'];
// append properties
foreach ($props as $k => $v) {
$line = wordwrap("$k:$v", 74, "\r\n ");
$ics_props[] = $line;
}
// append properties not fitting with common format
$ics_props[] = 'ORGANIZER;CN=' . $this->ics_escape($this->properties['organizer_name']) . ':' . $this->properties['organizer_url'];
// add reminder
$ics_props[] = 'BEGIN:VALARM';
$ics_props[] = 'TRIGGER:-PT1D';
$ics_props[] = 'ACTION:DISPLAY';
$ics_props[] = 'DESCRIPTION:Reminder';
$ics_props[] = 'END:VALARM';
// add footer
$ics_props[] = 'END:VEVENT';
$ics_props[] = 'END:VCALENDAR';
return implode("\r\n", $ics_props);
}
/**
* Utility functions
*/
private function sanitize($val, $key = false)
{
switch ($key) {
case 'start':
case 'end':
return $this->format_time($val);
}
return esc_attr($val);
}
private function format_time($time, $format = self::DT_FORMAT)
{
$dt = new DateTime($time);
return $dt->format($format);
}
private function ics_escape($str) {
return preg_replace('/([\,;])/','\\\$1', $str);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment