$c, 'account' => $a[0] ]; $channel_login = $c['channel_id']; } } } if(! $record) continue; if($record) { $verified = HTTPSig::verify('',$record['channel']['channel_pubkey']); if(! ($verified && $verified['header_signed'] && $verified['header_valid'])) { $record = null; } if($record['account']) { authenticate_success($record['account']); if($channel_login) { change_channel($channel_login); } } break; } } } } } /** * This server combines both CardDAV and CalDAV functionality into a single * server. It is assumed that the server runs at the root of a HTTP domain (be * that a domainname-based vhost or a specific TCP port. * * This example also assumes that you're using SQLite and the database has * already been setup (along with the database tables). * * You may choose to use MySQL instead, just change the PDO connection * statement. */ /** * UTC or GMT is easy to work with, and usually recommended for any * application. */ date_default_timezone_set('UTC'); /** * Make sure this setting is turned on and reflect the root url for your WebDAV * server. * * This can be for example the root / or a complete path to your server script. */ $baseUri = '/cdav/'; /** * Database * */ $pdo = \DBA::$dba->db; // Autoloader require_once 'vendor/autoload.php'; /** * The backends. Yes we do really need all of them. * * This allows any developer to subclass just any of them and hook into their * own backend systems. */ $auth = new \Zotlabs\Storage\BasicAuth(); $auth->setRealm(ucfirst(\Zotlabs\Lib\System::get_platform_name()) . 'CalDAV/CardDAV'); if(local_channel()) { logger('loggedin'); if((argv(1) == 'addressbooks') && (!Apps::system_app_installed(local_channel(), 'CardDAV'))) { killme(); } $channel = App::get_channel(); $auth->setCurrentUser($channel['channel_address']); $auth->channel_id = $channel['channel_id']; $auth->channel_hash = $channel['channel_hash']; $auth->channel_account_id = $channel['channel_account_id']; if($channel['channel_timezone']) $auth->setTimezone($channel['channel_timezone']); $auth->observer = $channel['channel_hash']; $principalUri = 'principals/' . $channel['channel_address']; if(! cdav_principal($principalUri)) { $this->activate($pdo, $channel); if(! cdav_principal($principalUri)) { return; } } } // Track CDAV updates from remote clients $httpmethod = $_SERVER['REQUEST_METHOD']; if($httpmethod === 'PUT' || $httpmethod === 'DELETE') { $channel = channelx_by_nick(argv(2)); $principalUri = 'principals/' . $channel['channel_address']; $httpuri = $_SERVER['REQUEST_URI']; logger("debug: method: " . $httpmethod, LOGGER_DEBUG); logger("debug: uri: " . $httpuri, LOGGER_DEBUG); if(strpos($httpuri, 'cdav/addressbooks') !== false) { $sync = 'addressbook'; $cdavtable = 'addressbooks'; } elseif(strpos($httpuri, 'cdav/calendars') !== false) { $sync = 'calendar'; $cdavtable = 'calendarinstances'; } else { $sync = false; } if($sync) { $uri = basename($httpuri); $httpbody = file_get_contents('php://input'); logger("debug: body: " . $httpbody, LOGGER_DEBUG); if($x = get_cdav_id($principalUri, argv(3), $cdavtable)) { $cdavdata = $this->get_cdav_data($x['id'], $cdavtable); $etag = (isset($_SERVER['HTTP_IF_MATCH']) ? $_SERVER['HTTP_IF_MATCH'] : false); // delete if($httpmethod === 'DELETE' && $etag && isset($cdavdata['etag']) && $cdavdata['etag'] == $etag) { Libsync::build_sync_packet($channel['channel_id'], [ $sync => [ 'action' => 'delete_card', 'uri' => $cdavdata['uri'], 'carduri' => $uri ] ]); } else { if($etag && isset($cdavdata['etag']) && $cdavdata['etag'] !== $etag) { // update Libsync::build_sync_packet($channel['channel_id'], [ $sync => [ 'action' => 'update_card', 'uri' => $cdavdata['uri'], 'carduri' => $uri, 'card' => $httpbody ] ]); } else { // new Libsync::build_sync_packet($channel['channel_id'], [ $sync => [ 'action' => 'import', 'uri' => $cdavdata['uri'], 'ids' => [ $uri ], 'card' => $httpbody ] ]); } } } } } $principalBackend = new \Sabre\DAVACL\PrincipalBackend\PDO($pdo); $carddavBackend = new \Sabre\CardDAV\Backend\PDO($pdo); $caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); /** * The directory tree * * Basically this is an array which contains the 'top-level' directories in the * WebDAV server. */ $nodes = [ // /principals new \Sabre\CalDAV\Principal\Collection($principalBackend), // /calendars new \Sabre\CalDAV\CalendarRoot($principalBackend, $caldavBackend), // /addressbook new \Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend) ]; // The object tree needs in turn to be passed to the server class $server = new \Sabre\DAV\Server($nodes); if(isset($baseUri)) $server->setBaseUri($baseUri); // Plugins $server->addPlugin(new \Sabre\DAV\Auth\Plugin($auth)); // $server->addPlugin(new \Sabre\DAV\Browser\Plugin()); $server->addPlugin(new \Sabre\DAV\Sync\Plugin()); $server->addPlugin(new \Sabre\DAV\Sharing\Plugin()); $server->addPlugin(new \Sabre\DAVACL\Plugin()); // CalDAV plugins $server->addPlugin(new \Sabre\CalDAV\Plugin()); $server->addPlugin(new \Sabre\CalDAV\SharingPlugin()); // $server->addPlugin(new \Sabre\CalDAV\Schedule\Plugin()); $server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); // CardDAV plugins $server->addPlugin(new \Sabre\CardDAV\Plugin()); $server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin()); // And off we go! $server->start(); killme(); } } function post() { if(! local_channel()) return; if((argv(1) === 'addressbook') && (! Apps::system_app_installed(local_channel(), 'CardDAV'))) { return; } $channel = App::get_channel(); $principalUri = 'principals/' . $channel['channel_address']; if(!cdav_principal($principalUri)) return; $pdo = \DBA::$dba->db; require_once 'vendor/autoload.php'; if(argc() == 2 && argv(1) === 'calendar') { $caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); $calendars = $caldavBackend->getCalendarsForUser($principalUri); //create new calendar if((isset($_REQUEST['{DAV:}displayname']) && $_REQUEST['{DAV:}displayname']) && (isset($_REQUEST['create']) && $_REQUEST['create'])) { do { $duplicate = false; $calendarUri = random_string(40); $r = q("SELECT uri FROM calendarinstances WHERE principaluri = '%s' AND uri = '%s' LIMIT 1", dbesc($principalUri), dbesc($calendarUri) ); if (count($r)) $duplicate = true; } while ($duplicate == true); $properties = [ '{DAV:}displayname' => escape_tags($_REQUEST['{DAV:}displayname']), '{http://apple.com/ns/ical/}calendar-color' => escape_tags($_REQUEST['color']), '{urn:ietf:params:xml:ns:caldav}calendar-description' => escape_tags($channel['channel_name']) ]; $id = $caldavBackend->createCalendar($principalUri, $calendarUri, $properties); // set new calendar to be visible set_pconfig(local_channel(), 'cdav_calendar' , $id[0], 1); Libsync::build_sync_packet($channel['channel_id'], [ 'calendar' => [ 'action' => 'create', 'uri' => $calendarUri, 'properties' => $properties ] ]); } //create new calendar object via ajax request if((isset($_REQUEST['submit']) && $_REQUEST['submit'] === 'create_event') && $_REQUEST['title'] && $_REQUEST['target'] && $_REQUEST['dtstart']) { $id = explode(':', $_REQUEST['target']); if(!cdav_perms($id[0],$calendars,true)) return; $cdavdata = $this->get_cdav_data($id[0], 'calendarinstances'); $timezone = ((x($_POST,'timezone_select')) ? escape_tags(trim($_POST['timezone_select'])) : ''); $tz = (($timezone) ? $timezone : date_default_timezone_get()); $allday = $_REQUEST['allday']; $title = escape_tags($_REQUEST['title']); $start = datetime_convert('UTC', 'UTC', $_REQUEST['dtstart']); $dtstart = new \DateTime($start); if($_REQUEST['dtend']) { $end = datetime_convert('UTC', 'UTC', $_REQUEST['dtend']); $dtend = new \DateTime($end); } $description = escape_tags($_REQUEST['description']); $location = escape_tags($_REQUEST['location']); do { $duplicate = false; $objectUri = random_string(40) . '.ics'; $r = q("SELECT uri FROM calendarobjects WHERE calendarid = %s AND uri = '%s' LIMIT 1", intval($id[0]), dbesc($objectUri) ); if (count($r)) $duplicate = true; } while ($duplicate == true); $vcalendar = new \Sabre\VObject\Component\VCalendar([ 'VEVENT' => [ 'SUMMARY' => $title, 'DTSTART' => $dtstart ] ]); if($dtend) { $vcalendar->VEVENT->add('DTEND', $dtend); if($allday) $vcalendar->VEVENT->DTEND['VALUE'] = 'DATE'; else $vcalendar->VEVENT->DTEND['TZID'] = $tz; } if($description) $vcalendar->VEVENT->add('DESCRIPTION', $description); if($location) $vcalendar->VEVENT->add('LOCATION', $location); if($allday) $vcalendar->VEVENT->DTSTART['VALUE'] = 'DATE'; else $vcalendar->VEVENT->DTSTART['TZID'] = $tz; $calendarData = $vcalendar->serialize(); $caldavBackend->createCalendarObject($id, $objectUri, $calendarData); Libsync::build_sync_packet($channel['channel_id'], [ 'calendar' => [ 'action' => 'import', 'uri' => $cdavdata['uri'], 'ids' => [ $objectUri ], 'card' => $calendarData ] ]); killme(); } //edit calendar name and color if((isset($_REQUEST['{DAV:}displayname']) && $_REQUEST['{DAV:}displayname']) && $_REQUEST['edit'] && $_REQUEST['id']) { $id = explode(':', $_REQUEST['id']); if(! cdav_perms($id[0],$calendars)) return; $cdavdata = $this->get_cdav_data($id[0], 'calendarinstances'); $mutations = [ '{DAV:}displayname' => escape_tags($_REQUEST['{DAV:}displayname']), '{http://apple.com/ns/ical/}calendar-color' => escape_tags($_REQUEST['color']) ]; $patch = new \Sabre\DAV\PropPatch($mutations); $caldavBackend->updateCalendar($id, $patch); $patch->commit(); Libsync::build_sync_packet($channel['channel_id'], [ 'calendar' => [ 'action' => 'edit', 'uri' => $cdavdata['uri'], 'mutations' => $mutations, ] ]); } //edit calendar object via ajax request if((isset($_REQUEST['submit']) && $_REQUEST['submit'] === 'update_event') && $_REQUEST['uri'] && $_REQUEST['title'] && $_REQUEST['target'] && $_REQUEST['dtstart']) { $id = explode(':', $_REQUEST['target']); if(! cdav_perms($id[0],$calendars,true)) return; $cdavdata = $this->get_cdav_data($id[0], 'calendarinstances'); $timezone = ((x($_POST,'timezone_select')) ? escape_tags(trim($_POST['timezone_select'])) : ''); $tz = (($timezone) ? $timezone : date_default_timezone_get()); $allday = intval($_REQUEST['allday']); $uri = escape_tags($_REQUEST['uri']); $title = escape_tags($_REQUEST['title']); $start = datetime_convert('UTC', 'UTC', $_REQUEST['dtstart']); $dtstart = new \DateTime($start); if($_REQUEST['dtend']) { $end = datetime_convert('UTC', 'UTC', $_REQUEST['dtend']); $dtend = new \DateTime($end); } $description = escape_tags($_REQUEST['description']); $location = escape_tags($_REQUEST['location']); $object = $caldavBackend->getCalendarObject($id, $uri); $vcalendar = \Sabre\VObject\Reader::read($object['calendardata']); if($title) $vcalendar->VEVENT->SUMMARY = $title; if($dtstart) { $vcalendar->VEVENT->DTSTART = $dtstart; if($allday) $vcalendar->VEVENT->DTSTART['VALUE'] = 'DATE'; else $vcalendar->VEVENT->DTSTART['TZID'] = $tz; } if($dtend) { $vcalendar->VEVENT->DTEND = $dtend; if($allday) $vcalendar->VEVENT->DTEND['VALUE'] = 'DATE'; else $vcalendar->VEVENT->DTEND['TZID'] = $tz; } else unset($vcalendar->VEVENT->DTEND); if($description) $vcalendar->VEVENT->DESCRIPTION = $description; if($location) $vcalendar->VEVENT->LOCATION = $location; $calendarData = $vcalendar->serialize(); $caldavBackend->updateCalendarObject($id, $uri, $calendarData); Libsync::build_sync_packet($channel['channel_id'], [ 'calendar' => [ 'action' => 'update_card', 'uri' => $cdavdata['uri'], 'carduri' => $uri, 'card' => $calendarData ] ]); killme(); } //delete calendar object via ajax request if($_REQUEST['delete'] && $_REQUEST['uri'] && $_REQUEST['target']) { $id = explode(':', $_REQUEST['target']); if(! cdav_perms($id[0],$calendars,true)) return; $cdavdata = $this->get_cdav_data($id[0], 'calendarinstances'); $uri = $_REQUEST['uri']; $caldavBackend->deleteCalendarObject($id, $uri); Libsync::build_sync_packet($channel['channel_id'], [ 'calendar' => [ 'action' => 'delete_card', 'uri' => $cdavdata['uri'], 'carduri' => $uri ] ]); killme(); } //edit calendar object date/timeme via ajax request (drag and drop) if($_REQUEST['update'] && $_REQUEST['id'] && $_REQUEST['uri']) { $id = [$_REQUEST['id'][0], $_REQUEST['id'][1]]; if(! cdav_perms($id[0],$calendars,true)) return; $cdavdata = $this->get_cdav_data($id[0], 'calendarinstances'); $timezone = ((x($_POST,'timezone_select')) ? escape_tags(trim($_POST['timezone_select'])) : ''); $tz = (($timezone) ? $timezone : date_default_timezone_get()); $allday = $_REQUEST['allday']; $uri = $_REQUEST['uri']; $start = datetime_convert('UTC', 'UTC', $_REQUEST['dtstart']); $dtstart = new \DateTime($start); if($_REQUEST['dtend']) { $end = datetime_convert('UTC', 'UTC', $_REQUEST['dtend']); $dtend = new \DateTime($end); } $object = $caldavBackend->getCalendarObject($id, $uri); $vcalendar = \Sabre\VObject\Reader::read($object['calendardata']); if($dtstart) { $vcalendar->VEVENT->DTSTART = $dtstart; if($allday) $vcalendar->VEVENT->DTSTART['VALUE'] = 'DATE'; else $vcalendar->VEVENT->DTSTART['TZID'] = $tz; } if($dtend) { $vcalendar->VEVENT->DTEND = $dtend; if($allday) $vcalendar->VEVENT->DTEND['VALUE'] = 'DATE'; else $vcalendar->VEVENT->DTEND['TZID'] = $tz; } else unset($vcalendar->VEVENT->DTEND); $calendarData = $vcalendar->serialize(); $caldavBackend->updateCalendarObject($id, $uri, $calendarData); Libsync::build_sync_packet($channel['channel_id'], [ 'calendar' => [ 'action' => 'update_card', 'uri' => $cdavdata['uri'], 'carduri' => $uri, 'card' => $calendarData ] ]); killme(); } //share a calendar - this only works on local system (with channels on the same server) if($_REQUEST['sharee'] && $_REQUEST['share']) { $id = [intval($_REQUEST['calendarid']), intval($_REQUEST['instanceid'])]; if(! cdav_perms($id[0],$calendars)) return; $hash = $_REQUEST['sharee']; $sharee_arr = channelx_by_hash($hash); $sharee = new \Sabre\DAV\Xml\Element\Sharee(); $sharee->href = 'mailto:' . $sharee_arr['xchan_addr']; $sharee->principal = 'principals/' . $sharee_arr['channel_address']; $sharee->access = intval($_REQUEST['access']); $sharee->properties = ['{DAV:}displayname' => $channel['channel_name']]; $caldavBackend->updateInvites($id, [$sharee]); } } if(argc() >= 2 && argv(1) === 'addressbook') { $carddavBackend = new \Sabre\CardDAV\Backend\PDO($pdo); $addressbooks = $carddavBackend->getAddressBooksForUser($principalUri); //create new addressbook if($_REQUEST['{DAV:}displayname'] && $_REQUEST['create']) { do { $duplicate = false; $addressbookUri = random_string(20); $r = q("SELECT uri FROM addressbooks WHERE principaluri = '%s' AND uri = '%s' LIMIT 1", dbesc($principalUri), dbesc($addressbookUri) ); if (count($r)) $duplicate = true; } while ($duplicate == true); $properties = ['{DAV:}displayname' => escape_tags($_REQUEST['{DAV:}displayname'])]; $carddavBackend->createAddressBook($principalUri, $addressbookUri, $properties); Libsync::build_sync_packet($channel['channel_id'], [ 'addressbook' => [ 'action' => 'create', 'uri' => $addressbookUri, 'properties' => $properties ] ]); } //edit addressbook if($_REQUEST['{DAV:}displayname'] && $_REQUEST['edit'] && $_REQUEST['id']) { $id = intval($_REQUEST['id']); if(! cdav_perms($id,$addressbooks)) return; $cdavdata = $this->get_cdav_data($id, 'addressbooks'); $mutations = [ '{DAV:}displayname' => escape_tags($_REQUEST['{DAV:}displayname']) ]; $patch = new \Sabre\DAV\PropPatch($mutations); $carddavBackend->updateAddressBook($id, $patch); $patch->commit(); Libsync::build_sync_packet($channel['channel_id'], [ 'addressbook' => [ 'action' => 'edit', 'uri' => $cdavdata['uri'], 'mutations' => $mutations, ] ]); } //create addressbook card if($_REQUEST['create'] && $_REQUEST['target'] && $_REQUEST['fn']) { $id = $_REQUEST['target']; $cdavdata = $this->get_cdav_data($id, 'addressbooks'); do { $duplicate = false; $uri = random_string(40) . '.vcf'; $r = q("SELECT uri FROM cards WHERE addressbookid = %s AND uri = '%s' LIMIT 1", intval($id), dbesc($uri) ); if (count($r)) $duplicate = true; } while ($duplicate == true); //TODO: this mostly duplictes the procedure in update addressbook card. should move this part to a function to avoid duplication $fn = $_REQUEST['fn']; $vcard = new \Sabre\VObject\Component\VCard([ 'FN' => $fn, 'N' => array_reverse(explode(' ', $fn)) ]); $fields = $this->request_to_array($_REQUEST); process_cdav_card($fields, $vcard); $cardData = $vcard->serialize(); $carddavBackend->createCard($id, $uri, $cardData); Libsync::build_sync_packet($channel['channel_id'], [ 'addressbook' => [ 'action' => 'import', 'uri' => $cdavdata['uri'], 'ids' => [ $uri ], 'card' => $cardData ] ]); } //edit addressbook card if($_REQUEST['update'] && $_REQUEST['uri'] && $_REQUEST['target']) { $id = $_REQUEST['target']; if(! cdav_perms($id,$addressbooks)) return; $cdavdata = $this->get_cdav_data($id, 'addressbooks'); $uri = $_REQUEST['uri']; $object = $carddavBackend->getCard($id, $uri); $vcard = \Sabre\VObject\Reader::read($object['carddata']); $fn = $_REQUEST['fn']; if($fn) { $vcard->FN = $fn; $vcard->N = array_reverse(explode(' ', $fn)); } $fields = $this->request_to_array($_REQUEST); process_cdav_card($fields, $vcard, true); $cardData = $vcard->serialize(); $carddavBackend->updateCard($id, $uri, $cardData); Libsync::build_sync_packet($channel['channel_id'], [ 'addressbook' => [ 'action' => 'update_card', 'uri' => $cdavdata['uri'], 'carduri' => $uri, 'card' => $cardData ] ]); } //delete addressbook card if($_REQUEST['delete'] && $_REQUEST['uri'] && $_REQUEST['target']) { $id = $_REQUEST['target']; if(! cdav_perms($id,$addressbooks)) return; $cdavdata = $this->get_cdav_data($id, 'addressbooks'); $uri = $_REQUEST['uri']; $carddavBackend->deleteCard($id, $uri); Libsync::build_sync_packet($channel['channel_id'], [ 'addressbook' => [ 'action' => 'delete_card', 'uri' => $cdavdata['uri'], 'carduri' => $uri ] ]); } } //Import calendar or addressbook if(($_FILES) && array_key_exists('userfile',$_FILES) && intval($_FILES['userfile']['size']) && $_REQUEST['target']) { $src = $_FILES['userfile']['tmp_name']; if($src) { $carddata = @file_get_contents($src); if($_REQUEST['c_upload']) { if($_REQUEST['target'] == 'channel_calendar') { $result = parse_ical_file($src,local_channel()); if($result) info( t('Calendar entries imported.') . EOL); else notice( t('No calendar entries found.') . EOL); @unlink($src); return; } $id = explode(':', $_REQUEST['target']); $ext = 'ics'; $table = 'calendarobjects'; $column = 'calendarid'; $sync = 'calendar'; $objects = new \Sabre\VObject\Splitter\ICalendar($carddata); $profile = \Sabre\VObject\Node::PROFILE_CALDAV; $backend = new \Sabre\CalDAV\Backend\PDO($pdo); $cdavdata = $this->get_cdav_data($id, 'calendarinstances'); } if($_REQUEST['a_upload']) { $id = intval($_REQUEST['target']); $ext = 'vcf'; $table = 'cards'; $column = 'addressbookid'; $sync = 'addressbook'; $objects = new \Sabre\VObject\Splitter\VCard($carddata); $profile = \Sabre\VObject\Node::PROFILE_CARDDAV; $backend = new \Sabre\CardDAV\Backend\PDO($pdo); $cdavdata = $this->get_cdav_data($id, 'addressbooks'); } $ids = []; import_cdav_card($id, $ext, $table, $column, $objects, $profile, $backend, $ids, true); Libsync::build_sync_packet($channel['channel_id'], [ $sync => [ 'action' => 'import', 'uri' => $cdavdata['uri'], 'ids' => $ids, 'card' => $carddata ] ]); } @unlink($src); } } function get() { if(!local_channel()) return; if((argv(1) === 'addressbook') && (! Apps::system_app_installed(local_channel(), 'CardDAV'))) { //Do not display any associated widgets at this point App::$pdl = ''; $papp = Apps::get_papp('CardDAV'); return Apps::app_render($papp, 'module'); } App::$profile_uid = local_channel(); $channel = App::get_channel(); $principalUri = 'principals/' . $channel['channel_address']; $pdo = \DBA::$dba->db; require_once 'vendor/autoload.php'; if(!cdav_principal($principalUri)) { $this->activate($pdo, $channel); if(!cdav_principal($principalUri)) { return; } } if(argv(1) === 'calendar') { nav_set_selected('Calendar'); $caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); $calendars = $caldavBackend->getCalendarsForUser($principalUri); } //Display calendar(s) here if(argc() <= 3 && argv(1) === 'calendar') { head_add_js('/library/fullcalendar/dist/index.global.js'); head_add_css('cdav_calendar.css'); $o = ''; $sources = ''; $resource_id = ''; $resource = null; if(argc() == 3) $resource_id = argv(2); $categories = ''; if($resource_id) { $r = q("SELECT event.*, item.author_xchan, item.owner_xchan, item.plink, item.id as item_id FROM event LEFT JOIN item ON event.event_hash = item.resource_id WHERE event.uid = %d AND event.event_hash = '%s' LIMIT 1", intval(local_channel()), dbesc($resource_id) ); if($r) { xchan_query($r); $r = fetch_post_tags($r,true); $tz = get_iconfig($r[0], 'event', 'timezone'); if(! $tz) $tz = 'UTC'; $r[0]['timezone'] = $tz; $r[0]['dtstart'] = (($r[0]['adjust']) ? datetime_convert('UTC', date_default_timezone_get(), $r[0]['dtstart'], 'c') : datetime_convert('UTC', 'UTC', $r[0]['dtstart'], 'c')); $r[0]['dtend'] = (($r[0]['adjust']) ? datetime_convert('UTC', date_default_timezone_get(), $r[0]['dtend'], 'c') : datetime_convert('UTC', 'UTC' ,$r[0]['dtend'], 'c')); $r[0]['plink'] = [$r[0]['plink'], t('Link to source')]; $resource = $r[0]; $catsenabled = feature_enabled(local_channel(),'categories'); if($catsenabled){ if($r[0]['term']) { $cats = get_terms_oftype($r[0]['term'], TERM_CATEGORY); foreach ($cats as $cat) { if(strlen($categories)) $categories .= ', '; $categories .= $cat['term']; } } } if($r[0]['dismissed'] == 0) { q("UPDATE event SET dismissed = 1 WHERE event.uid = %d AND event.event_hash = '%s'", intval(local_channel()), dbesc($resource_id) ); } } } if(get_pconfig(local_channel(), 'cdav_calendar', 'channel_calendar')) { $sources .= '{ id: \'channel_calendar\', url: \'/channel_calendar/json/\', color: \'#3a87ad\' }, '; } $channel_calendars[] = [ 'displayname' => $channel['channel_name'], 'id' => 'channel_calendar' ]; foreach($calendars as $calendar) { $editable = (($calendar['share-access'] == 2) ? 'false' : 'true'); // false/true must be string since we're passing it to javascript $color = (($calendar['{http://apple.com/ns/ical/}calendar-color']) ? $calendar['{http://apple.com/ns/ical/}calendar-color'] : '#6cad39'); $sharer = (($calendar['share-access'] == 3) ? $calendar['{urn:ietf:params:xml:ns:caldav}calendar-description'] : ''); $switch = get_pconfig(local_channel(), 'cdav_calendar', $calendar['id'][0]); if($switch) { $sources .= '{ id: ' . $calendar['id'][0] . ', url: \'/cdav/calendar/json/' . $calendar['id'][0] . '/' . $calendar['id'][1] . '\', color: \'' . $color . '\' }, '; } if($calendar['share-access'] != 2) { $writable_calendars[] = [ 'displayname' => $calendar['{DAV:}displayname'], 'sharer' => $sharer, 'id' => $calendar['id'] ]; } } $sources = rtrim($sources, ', '); $first_day = feature_enabled(local_channel(), 'cal_first_day'); $first_day = (($first_day) ? $first_day : 0); $title = ['title', t('Event title') ]; $dtstart = ['dtstart', t('Start date and time')]; $dtend = ['dtend', t('End date and time')]; $timezone_select = ['timezone_select' , t('Timezone:'), date_default_timezone_get(), '', get_timezones()]; $description = ['description', t('Description')]; $location = ['location', t('Location')]; $catsenabled = feature_enabled(local_channel(), 'categories'); require_once('include/acl_selectors.php'); $accesslist = new \Zotlabs\Access\AccessList($channel); $perm_defaults = $accesslist->get(); //$acl = (($orig_event['event_xchan']) ? '' : populate_acl(((x($orig_event)) ? $orig_event : $perm_defaults), false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'))); $acl = populate_acl($perm_defaults, false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream')); $permissions = (($resource_id) ? $resource : $perm_defaults); $o .= replace_macros(get_markup_template('cdav_calendar.tpl'), [ '$sources' => $sources, '$color' => $color, '$lang' => App::$language, '$timezone' => date_default_timezone_get(), '$first_day' => $first_day, '$prev' => t('Previous'), '$next' => t('Next'), '$today' => t('Today'), '$month' => t('Month'), '$week' => t('Week'), '$day' => t('Day'), '$list_month' => t('List month'), '$list_week' => t('List week'), '$list_day' => t('List day'), '$title' => $title, '$channel_calendars' => $channel_calendars, '$writable_calendars' => $writable_calendars, '$dtstart' => $dtstart, '$dtend' => $dtend, '$description' => $description, '$location' => $location, '$more' => t('More'), '$less' => t('Less'), '$update' => t('Update'), '$calendar_select_label' => t('Select calendar'), '$calendar_optiopns_label' => [t('Channel Calendars'), t('CalDAV Calendars')], '$delete' => t('Delete'), '$delete_all' => t('Delete all'), '$cancel' => t('Cancel'), '$create' => t('Create'), '$recurrence_warning' => t('Sorry! Editing of recurrent events is not yet implemented.'), '$disabled_warning' => t('Could not fetch calendar resource. The selected calendar might be disabled.'), '$channel_hash' => $channel['channel_hash'], '$acl' => $acl, '$lockstate' => (($accesslist->is_private()) ? 'lock' : 'unlock'), '$allow_cid' => acl2json($permissions['allow_cid']), '$allow_gid' => acl2json($permissions['allow_gid']), '$deny_cid' => acl2json($permissions['deny_cid']), '$deny_gid' => acl2json($permissions['deny_gid']), '$catsenabled' => $catsenabled, '$categories_label' => t('Categories'), '$resource' => json_encode($resource), '$categories' => $categories, '$timezone_select' => ((feature_enabled(local_channel(),'event_tz_select')) ? $timezone_select : '') ]); return $o; } //Provide json data for calendar if(argc() == 5 && argv(1) === 'calendar' && argv(2) === 'json' && intval(argv(3)) && intval(argv(4))) { $events = []; $id = [argv(3), argv(4)]; if(! cdav_perms($id[0],$calendars)) json_return_and_die($events); if (x($_GET,'start')) $start = new \DateTime($_GET['start']); if (x($_GET,'end')) $end = new \DateTime($_GET['end']); $filters['name'] = 'VCALENDAR'; $filters['prop-filters'][0]['name'] = 'VEVENT'; $filters['prop-filters'][0]['is-not-defined'] = null; $filters['prop-filters'][0]['param-filters'] = null; $filters['prop-filters'][0]['text-match'] = null; $filters['comp-filters'][0]['name'] = 'VEVENT'; $filters['comp-filters'][0]['is-not-defined'] = null; $filters['comp-filters'][0]['time-range']['start'] = $start; $filters['comp-filters'][0]['time-range']['end'] = $end; $uris = $caldavBackend->calendarQuery($id, $filters); if($uris) { $objects = $caldavBackend->getMultipleCalendarObjects($id, $uris); foreach($objects as $object) { $vcalendar = \Sabre\VObject\Reader::read($object['calendardata']); if(isset($vcalendar->VEVENT->RRULE)) { // expanding recurrent events seems to loose timezone info // save it here so we can add it later $recurrent_timezone = (string)$vcalendar->VEVENT->DTSTART['TZID']; $vcalendar = $vcalendar->expand($start, $end); } foreach($vcalendar->VEVENT as $vevent) { $title = (string)$vevent->SUMMARY; $dtstart = (string)$vevent->DTSTART; $dtend = (string)$vevent->DTEND; $description = (string)$vevent->DESCRIPTION; $location = (string)$vevent->LOCATION; $timezone_str = (string)$vevent->DTSTART['TZID']; $rw = ((cdav_perms($id[0],$calendars,true)) ? true : false); $editable = $rw ? true : false; $recurrent = ((isset($vevent->{'RECURRENCE-ID'})) ? true : false); if($recurrent) { $editable = false; $timezone_str = $recurrent_timezone; } // Try to get an usable olson format timezone $timezone_obj = \Sabre\VObject\TimeZoneUtil::getTimeZone($timezone_str, $vcalendar); $timezone = $timezone_obj->getName(); // If we got nothing fallback to UTC if(! $timezone) $timezone = 'UTC'; $allDay = (((string)$vevent->DTSTART['VALUE'] == 'DATE') ? true : false); $events[] = [ 'calendar_id' => $id, 'uri' => $object['uri'], 'title' => $title, 'timezone' => $timezone, 'start' => datetime_convert($timezone, date_default_timezone_get(), $dtstart, 'c'), 'end' => (($dtend) ? datetime_convert($timezone, date_default_timezone_get(), $dtend, 'c') : ''), 'description' => $description, 'location' => $location, 'allDay' => $allDay, 'editable' => $editable, 'recurrent' => $recurrent, 'rw' => $rw ]; } } } json_return_and_die($events); } //enable/disable calendars if(argc() == 5 && argv(1) === 'calendar' && argv(2) === 'switch' && argv(3) && (argv(4) == 1 || argv(4) == 0)) { $id = argv(3); if(! cdav_perms($id,$calendars)) killme(); $cdavdata = $this->get_cdav_data($id, 'calendarinstances'); set_pconfig(local_channel(), 'cdav_calendar', $id, argv(4)); Libsync::build_sync_packet(local_channel(), [ 'calendar' => [ 'action' => 'switch', 'uri' => $cdavdata['uri'], 'switch' => intval(argv(4)) ] ]); killme(); } //drop calendar if(argc() == 5 && argv(1) === 'calendar' && argv(2) === 'drop' && intval(argv(3)) && intval(argv(4))) { $id = [argv(3), argv(4)]; if(! cdav_perms($id[0],$calendars)) killme(); // get metadata before we delete it $cdavdata = $this->get_cdav_data($id[0], 'calendarinstances'); $caldavBackend->deleteCalendar($id); Libsync::build_sync_packet($channel['channel_id'], [ 'calendar' => [ 'action' => 'drop', 'uri' => $cdavdata['uri'] ] ]); killme(); } //drop sharee if(argc() == 6 && argv(1) === 'calendar' && argv(2) === 'dropsharee' && intval(argv(3)) && intval(argv(4))) { $id = [argv(3), argv(4)]; $hash = argv(5); if(! cdav_perms($id[0],$calendars)) killme(); $sharee_arr = channelx_by_hash($hash); $sharee = new \Sabre\DAV\Xml\Element\Sharee(); $sharee->href = 'mailto:' . $sharee_arr['xchan_addr']; $sharee->principal = 'principals/' . $sharee_arr['channel_address']; $sharee->access = 4; $caldavBackend->updateInvites($id, [$sharee]); killme(); } if(argv(1) === 'addressbook') { nav_set_selected('CardDAV'); $carddavBackend = new \Sabre\CardDAV\Backend\PDO($pdo); $addressbooks = $carddavBackend->getAddressBooksForUser($principalUri); } //Display Adressbook here if(argc() == 3 && argv(1) === 'addressbook' && intval(argv(2))) { $id = argv(2); $displayname = cdav_perms($id,$addressbooks); if(!$displayname) return; head_add_css('cdav_addressbook.css'); $o = ''; $sabrecards = $carddavBackend->getCards($id); foreach($sabrecards as $sabrecard) { $uris[] = $sabrecard['uri']; } if($uris) { $objects = $carddavBackend->getMultipleCards($id, $uris); foreach($objects as $object) { $vcard = \Sabre\VObject\Reader::read($object['carddata']); $photo = ''; if($vcard->PHOTO) { $photo_value = strtolower($vcard->PHOTO->getValueType()); // binary or uri if($photo_value === 'binary') { $photo_type = strtolower($vcard->PHOTO['TYPE']); // mime jpeg, png or gif $photo = 'data:image/' . $photo_type . ';base64,' . base64_encode((string)$vcard->PHOTO); } else { $url = parse_url((string)$vcard->PHOTO); $photo = 'data:' . $url['path']; } } $fn = ''; if($vcard->FN) { $fn = (string)$vcard->FN; } $org = ''; if($vcard->ORG) { $org = (string)$vcard->ORG; } $title = ''; if($vcard->TITLE) { $title = (string)$vcard->TITLE; } $tels = []; if($vcard->TEL) { foreach($vcard->TEL as $tel) { $type = (($tel['TYPE']) ? translate_type((string)$tel['TYPE']) : ''); $tels[] = [ 'type' => $type, 'nr' => (string)$tel ]; } } $emails = []; if($vcard->EMAIL) { foreach($vcard->EMAIL as $email) { $type = (($email['TYPE']) ? translate_type((string)$email['TYPE']) : ''); $emails[] = [ 'type' => $type, 'address' => (string)$email ]; } } $impps = []; if($vcard->IMPP) { foreach($vcard->IMPP as $impp) { $type = (($impp['TYPE']) ? translate_type((string)$impp['TYPE']) : ''); $impps[] = [ 'type' => $type, 'address' => (string)$impp ]; } } $urls = []; if($vcard->URL) { foreach($vcard->URL as $url) { $type = (($url['TYPE']) ? translate_type((string)$url['TYPE']) : ''); $urls[] = [ 'type' => $type, 'address' => (string)$url ]; } } $adrs = []; if($vcard->ADR) { foreach($vcard->ADR as $adr) { $type = (($adr['TYPE']) ? translate_type((string)$adr['TYPE']) : ''); $adrs[] = [ 'type' => $type, 'address' => $adr->getParts() ]; } } $note = ''; if($vcard->NOTE) { $note = (string)$vcard->NOTE; } $cards[] = [ 'id' => $object['id'], 'uri' => $object['uri'], 'photo' => $photo, 'fn' => $fn, 'org' => $org, 'title' => $title, 'tels' => $tels, 'emails' => $emails, 'impps' => $impps, 'urls' => $urls, 'adrs' => $adrs, 'note' => $note ]; } usort($cards, function($a, $b) { return strcasecmp($a['fn'], $b['fn']); }); } $o .= replace_macros(get_markup_template('cdav_addressbook.tpl'), [ '$id' => $id, '$cards' => $cards, '$displayname' => $displayname, '$name_label' => t('Name'), '$org_label' => t('Organisation'), '$title_label' => t('Title'), '$tel_label' => t('Phone'), '$email_label' => t('Email'), '$impp_label' => t('Instant messenger'), '$url_label' => t('Website'), '$adr_label' => t('Address'), '$note_label' => t('Note'), '$mobile' => t('Mobile'), '$home' => t('Home'), '$work' => t('Work'), '$other' => t('Other'), '$add_card' => t('Add Contact'), '$add_field' => t('Add Field'), '$create' => t('Create'), '$update' => t('Update'), '$delete' => t('Delete'), '$cancel' => t('Cancel'), '$po_box' => t('P.O. Box'), '$extra' => t('Additional'), '$street' => t('Street'), '$locality' => t('Locality'), '$region' => t('Region'), '$zip_code' => t('ZIP Code'), '$country' => t('Country') ]); return $o; } //delete addressbook if(argc() > 3 && argv(1) === 'addressbook' && argv(2) === 'drop' && intval(argv(3))) { $id = argv(3); if(! cdav_perms($id,$addressbooks)) return; // get metadata before we delete it $cdavdata = $this->get_cdav_data($id, 'addressbooks'); $carddavBackend->deleteAddressBook($id); if($cdavdata) Libsync::build_sync_packet($channel['channel_id'], [ 'addressbook' => [ 'action' => 'drop', 'uri' => $cdavdata['uri'] ] ]); killme(); } } function activate($pdo, $channel) { if(! $channel) return; $uri = 'principals/' . $channel['channel_address']; $r = q("select * from principals where uri = '%s' limit 1", dbesc($uri) ); if($r) { $r = q("update principals set email = '%s', displayname = '%s' where uri = '%s' ", dbesc($channel['xchan_addr']), dbesc($channel['channel_name']), dbesc($uri) ); } else { $r = q("insert into principals ( uri, email, displayname ) values('%s','%s','%s') ", dbesc($uri), dbesc($channel['xchan_addr']), dbesc($channel['channel_name']) ); //create default calendar $caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); $properties = [ '{DAV:}displayname' => t('Default Calendar'), '{http://apple.com/ns/ical/}calendar-color' => '#6cad39', '{urn:ietf:params:xml:ns:caldav}calendar-description' => $channel['channel_name'] ]; $id = $caldavBackend->createCalendar($uri, 'default', $properties); set_pconfig(local_channel(), 'cdav_calendar' , $id[0], 1); set_pconfig(local_channel(), 'cdav_calendar' , 'channel_calendar', 1); //create default addressbook $carddavBackend = new \Sabre\CardDAV\Backend\PDO($pdo); $properties = ['{DAV:}displayname' => t('Default Addressbook')]; $carddavBackend->createAddressBook($uri, 'default', $properties); } } function get_cdav_data($id, $table) { $r = q("SELECT * FROM $table WHERE id = %d LIMIT 1", intval($id) ); if(! $r) return false; return $r[0]; } function request_to_array($req) { $f = []; $f['org'] = $req['org']; $f['title'] = $req['title']; $f['tel'] = $req['tel']; $f['tel_type'] = $req['tel_type']; $f['email'] = $req['email']; $f['email_type'] = $req['email_type']; $f['impp'] = $req['impp']; $f['impp_type'] = $req['impp_type']; $f['url'] = $req['url']; $f['url_type'] = $req['url_type']; $f['adr'] = $req['adr']; $f['adr_type'] = $req['adr_type']; $f['note'] = $req['note']; return $f; } }