Created
November 25, 2022 14:47
-
-
Save Accudio/24da23ed7399f9298b9319c39b8ab9cc to your computer and use it in GitHub Desktop.
Class to generate Calendar URLs and files
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 | |
/** | |
* 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