/** * @return array */ private function getICalPeriods() { $response = []; $iCalLink = $this->checkForICalHref(); if ($this->checkAccessToFile($iCalLink)) { $startArray = array(); $endArray = array(); require_once 'iCalcreator.php'; $v = new \vcalendar(); // initiate new CALENDAR $config = array("unique_id" => "ukrapts.com", "url" => "{$iCalLink}"); $v->setConfig($config); $v->parse(); $v->sort(); $i = 0; while ($comp = $v->getComponent("VEVENT")) { $dtstart_array = $comp->getProperty("DTSTART", 1, TRUE); $dtstart = $dtstart_array["value"]; $startDate = "{$dtstart["year"]}-{$dtstart["month"]}-{$dtstart["day"]}"; $dtend_array = $comp->getProperty("dtend", 1, TRUE); $dtend = $dtend_array["value"]; $endDate = "{$dtend["year"]}-{$dtend["month"]}-{$dtend["day"]}"; if ($endDate >= $this->DBQueries()->currentDate()) { $startArray[] = $startDate; $endArray[] = $endDate; } $i++; } sort($startArray); sort($endArray); $response['start'] = $startArray; $response['end'] = $endArray; } return $response; }
function _get_events_url(&$events, $url, $date) { $v = new vcalendar(); $v->setConfig('unique_id', 'barchat'); $v->setProperty('method', 'PUBLISH'); $v->setProperty("x-wr-calname", "Calendar Sample"); $v->setProperty("X-WR-CALDESC", "Calendar Description"); $v->setProperty("X-WR-TIMEZONE", "America/New_York"); $v->setConfig('url', $url); try { $v->parse(); } catch (exception $e) { } $v->sort(); $m = $date->format('n'); $d = $date->format('j'); $y = $date->format('Y'); $eventArray = $v->selectComponents($y, $m, 1, $y, $m, 31); foreach ((array) $eventArray as $yearkey => $yeararray) { foreach ((array) $yeararray as $monthkey => $montharray) { foreach ((array) $montharray as $daykey => $dayarray) { foreach ((array) $dayarray as $eventnumber => $event) { //echo "{$y}-{$m}-{$daykey} [{$eventnumber}]: "; $time = $event->dtstart['value']; $tz = $event->dtstart['params']['TZID'] == '' ? 'America/New_York' : $event->dtstart['params']['TZID']; if ($time['tz'] == 'Z') { $tz = 'GMT'; } if (isset($event->dtstart['params']['VALUE']) && $event->dtstart['params']['VALUE'] == 'DATE') { $allday = new DateTime("{$time['year']}-{$time['month']}-{$time['day']}", new DateTimeZone($tz)); $allday->setTimezone(new DateTimeZone('America/New_York')); $d = sprintf('%04d-%02d-%02d', $y, $m, $daykey); if (!is_array($events[$d])) { $events[$d] = array(); } $alldayint = intval($allday->format('U')); while (isset($events[$d][$alldayint])) { $alldayint++; } $events[$d][$alldayint] = '<span class="calendartime">All Day</span> ' . trim($event->summary['value']); //var_dump(date('r', $allday) . ' = ' . $allday); //var_dump($event->summary['value']); } else { if (isset($event->xprop['X-CURRENT-DTSTART'])) { $dt = new DateTime($event->xprop['X-CURRENT-DTSTART']['value'], new DateTimeZone($tz)); } else { $dt = new DateTime("{$time['year']}-{$time['month']}-{$time['day']} {$time['hour']}:{$time['min']}:{$time['sec']}", new DateTimeZone($tz)); } $dt->setTimezone(new DateTimeZone('America/New_York')); if (isset($event->xprop['X-CURRENT-DTEND'])) { $dte = new DateTime($event->xprop['X-CURRENT-DTEND']['value'], new DateTimeZone($tz)); } else { $timee = $event->dtstart['value']; $dte = new DateTime("{$timee['year']}-{$timee['month']}-{$timee['day']} {$timee['hour']}:{$timee['min']}:{$timee['sec']}", new DateTimeZone($tz)); } $dte->setTimezone(new DateTimeZone('America/New_York')); if (!is_array($events[$d])) { $events[$d] = array(); } $d = sprintf('%04d-%02d-%02d', $y, $m, $daykey); $daytime = $dt->format('U'); while (isset($events[$d][$daytime])) { $daytime++; } if ($dt->format('g:ia') != $dte->format('g:ia')) { $events[$d][$daytime] = '<span class="calendartime">' . $dt->format('g:ia') . ' - ' . $dte->format('g:ia') . '</span> ' . trim($event->summary['value']); } else { $events[$d][$daytime] = '<span class="calendartime">' . $dt->format('g:ia') . '</span> ' . trim($event->summary['value']); } //var_dump($event->dtstart); //var_dump($event->summary['value']); //var_dump($dt->format('r')); //var_dump($event); } } } } } }
/** * Generate ical file content * * @param $who user ID * @param $who_group group ID * * @return icalendar string **/ static function generateIcal($who, $who_group) { global $CFG_GLPI, $LANG; if ($who == 0 && $who_group == 0) { return false; } include_once GLPI_ROOT . "/lib/icalcreator/iCalcreator.class.php"; $v = new vcalendar(); if (!empty($CFG_GLPI["version"])) { $v->setConfig('unique_id', "GLPI-Planning-" . trim($CFG_GLPI["version"])); } else { $v->setConfig('unique_id', "GLPI-Planning-UnknownVersion"); } $v->setConfig('filename', "glpi.ics"); $v->setProperty("method", "PUBLISH"); $v->setProperty("version", "2.0"); $v->setProperty("x-wr-calname", "GLPI-" . $who . "-" . $who_group); $v->setProperty("calscale", "GREGORIAN"); $interv = array(); $begin = time() - MONTH_TIMESTAMP * 12; $end = time() + MONTH_TIMESTAMP * 12; $begin = date("Y-m-d H:i:s", $begin); $end = date("Y-m-d H:i:s", $end); // ---------------Tracking $interv = TicketPlanning::populatePlanning(array('who' => $who, 'who_group' => $who_group, 'begin' => $begin, 'end' => $end)); // ---------------Reminder $data = Reminder::populatePlanning(array('who' => $who, 'who_group' => $who_group, 'begin' => $begin, 'end' => $end)); $interv = array_merge($interv, $data); // ---------------Plugin $data = doHookFunction("planning_populate", array("begin" => $begin, "end" => $end, "who" => $who, "who_group" => $who_group)); if (isset($data["items"]) && count($data["items"])) { $interv = array_merge($data["items"], $interv); } if (count($interv) > 0) { foreach ($interv as $key => $val) { $vevent = new vevent(); //initiate EVENT if (isset($val["tickettasks_id"])) { $vevent->setProperty("uid", "Job#" . $val["tickettasks_id"]); } else { if (isset($val["reminders_id"])) { $vevent->setProperty("uid", "Event#" . $val["reminders_id"]); } else { if (isset($val['planningID'])) { // Specify the ID (for plugins) $vevent->setProperty("uid", "Plugin#" . $val['planningID']); } else { $vevent->setProperty("uid", "Plugin#" . $key); } } } $vevent->setProperty("dstamp", $val["begin"]); $vevent->setProperty("dtstart", $val["begin"]); $vevent->setProperty("dtend", $val["end"]); if (isset($val["tickets_id"])) { $vevent->setProperty("summary", $LANG['planning'][8] . " # " . $val["tickets_id"] . " " . $LANG['document'][14] . " # " . $val["device"]); } else { if (isset($val["name"])) { $vevent->setProperty("summary", $val["name"]); } } if (isset($val["content"])) { $vevent->setProperty("description", html_clean($val["content"])); } else { if (isset($val["name"])) { $vevent->setProperty("description", $val["name"]); } } if (isset($val["tickets_id"])) { $vevent->setProperty("url", $CFG_GLPI["url_base"] . "/index.php?redirect=tracking_" . $val["tickets_id"]); } $v->setComponent($vevent); } } $v->sort(); //$v->parse(); return $v->returnCalendar(); }
/** * Process vcalendar instance - add events to database. * * @param vcalendar $v Calendar to retrieve data from. * @param array $args Arbitrary arguments map. * * @throws Ai1ec_Parse_Exception * * @internal param stdClass $feed Instance of feed (see Ai1ecIcs plugin). * @internal param string $comment_status WP comment status: 'open' or 'closed'. * @internal param int $do_show_map Map display status (DB boolean: 0 or 1). * * @return int Count of events added to database. */ public function add_vcalendar_events_to_db(vcalendar $v, array $args) { $feed = isset($args['feed']) ? $args['feed'] : null; $comment_status = isset($args['comment_status']) ? $args['comment_status'] : 'open'; $do_show_map = isset($args['do_show_map']) ? $args['do_show_map'] : 0; $count = 0; $events_in_db = $args['events_in_db']; $v->sort(); // Reverse the sort order, so that RECURRENCE-IDs are listed before the // defining recurrence events, and therefore take precedence during // caching. $v->components = array_reverse($v->components); // TODO: select only VEVENT components that occur after, say, 1 month ago. // Maybe use $v->selectComponents(), which takes into account recurrence // Fetch default timezone in case individual properties don't define it $timezone = $v->getProperty('X-WR-TIMEZONE'); $timezone = (string) $timezone[1]; // go over each event while ($e = $v->getComponent('vevent')) { // Event data array. $data = array(); // ===================== // = Start & end times = // ===================== $start = $e->getProperty('dtstart', 1, true); $end = $e->getProperty('dtend', 1, true); // For cases where a "VEVENT" calendar component // specifies a "DTSTART" property with a DATE value type but none // of "DTEND" nor "DURATION" property, the event duration is taken to // be one day. For cases where a "VEVENT" calendar component // specifies a "DTSTART" property with a DATE-TIME value type but no // "DTEND" property, the event ends on the same calendar date and // time of day specified by the "DTSTART" property. if (empty($end)) { // #1 if duration is present, assign it to end time $end = $e->getProperty('duration', 1, true, true); if (empty($end)) { // #2 if only DATE value is set for start, set duration to 1 day if (!isset($start['value']['hour'])) { $end = array('value' => array('year' => $start['value']['year'], 'month' => $start['value']['month'], 'day' => $start['value']['day'] + 1, 'hour' => 0, 'min' => 0, 'sec' => 0)); if (isset($start['value']['tz'])) { $end['value']['tz'] = $start['value']['tz']; } } else { // #3 set end date to start time $end = $start; } } } $categories = $e->getProperty("CATEGORIES", false, true); $imported_cat = array(); // If the user chose to preserve taxonomies during import, add categories. if ($categories && $feed->keep_tags_categories) { $imported_cat = $this->_add_categories_and_tags($categories['value'], $imported_cat, false, true); } $feed_categories = $feed->feed_category; if (!empty($feed_categories)) { $imported_cat = $this->_add_categories_and_tags($feed_categories, $imported_cat, false, false); } $tags = $e->getProperty("X-TAGS", false, true); $imported_tags = array(); // If the user chose to preserve taxonomies during import, add tags. if ($tags && $feed->keep_tags_categories) { $imported_tags = $this->_add_categories_and_tags($tags[1]['value'], $imported_tags, true, true); } $feed_tags = $feed->feed_tags; if (!empty($feed_tags)) { $imported_tags = $this->_add_categories_and_tags($feed_tags, $imported_tags, true, true); } // Event is all-day if no time components are defined $allday = $this->_is_timeless($start['value']) && $this->_is_timeless($end['value']); // Also check the proprietary MS all-day field. $ms_allday = $e->getProperty('X-MICROSOFT-CDO-ALLDAYEVENT'); if (!empty($ms_allday) && $ms_allday[1] == 'TRUE') { $allday = true; } $start = $this->_time_array_to_datetime($start, $timezone); $end = $this->_time_array_to_datetime($end, $timezone); if (false === $start || false === $end) { throw new Ai1ec_Parse_Exception('Failed to parse one or more dates given timezone "' . var_export($timezone, true) . '"'); continue; } // If all-day, and start and end times are equal, then this event has // invalid end time (happens sometimes with poorly implemented iCalendar // exports, such as in The Event Calendar), so set end time to 1 day // after start time. if ($allday && $start->format() === $end->format()) { $end->adjust_day(+1); } $data += compact('start', 'end', 'allday'); // ======================================= // = Recurrence rules & recurrence dates = // ======================================= if ($rrule = $e->createRrule()) { $rrule = explode(':', $rrule); $rrule = trim(end($rrule)); } if ($exrule = $e->createExrule()) { $exrule = explode(':', $exrule); $exrule = trim(end($exrule)); } if ($rdate = $e->createRdate()) { $rdate = explode(':', $rdate); $rdate = trim(end($rdate)); } // =================== // = Exception dates = // =================== $exdate_array = array(); if ($exdates = $e->createExdate()) { // We may have two formats: // one exdate with many dates ot more EXDATE rules $exdates = explode("EXDATE", $exdates); foreach ($exdates as $exd) { if (empty($exd)) { continue; } $exploded = explode(':', $exd); $exdate_array[] = trim(end($exploded)); } } // This is the local string. $exdate_loc = implode(',', $exdate_array); $gmt_exdates = array(); // Now we convert the string to gmt. I must do it here // because EXDATE:date1,date2,date3 must be parsed if (!empty($exdate_loc)) { foreach (explode(',', $exdate_loc) as $date) { $gmt_exdates[] = substr((string) $date, 0, 8); } } $exdate = implode(',', $gmt_exdates); // ======================== // = Latitude & longitude = // ======================== $latitude = $longitude = NULL; $geo_tag = $e->getProperty('geo'); if (is_array($geo_tag)) { if (isset($geo_tag['latitude']) && isset($geo_tag['longitude'])) { $latitude = (double) $geo_tag['latitude']; $longitude = (double) $geo_tag['longitude']; } } else { if (!empty($geo_tag) && false !== strpos($geo_tag, ';')) { list($latitude, $longitude) = explode(';', $geo_tag, 2); $latitude = (double) $latitude; $longitude = (double) $longitude; } } unset($geo_tag); if (NULL !== $latitude) { $data += compact('latitude', 'longitude'); // Check the input coordinates checkbox, otherwise lat/long data // is not present on the edit event page $data['show_coordinates'] = 1; } // =================== // = Venue & address = // =================== $address = $venue = ''; $location = $e->getProperty('location'); $matches = array(); // This regexp matches a venue / address in the format // "venue @ address" or "venue - address". preg_match('/\\s*(.*\\S)\\s+[\\-@]\\s+(.*)\\s*/', $location, $matches); // if there is no match, it's not a combined venue + address if (empty($matches)) { // if there is a comma, probably it's an address if (false === strpos($location, ',')) { $venue = $location; } else { $address = $location; } } else { $venue = isset($matches[1]) ? $matches[1] : ''; $address = isset($matches[2]) ? $matches[2] : ''; } // ===================================================== // = Set show map status based on presence of location = // ===================================================== if (1 === $do_show_map && NULL === $latitude && empty($address)) { $do_show_map = 0; } // ================== // = Cost & tickets = // ================== $cost = $e->getProperty('X-COST'); $cost = $cost ? $cost[1] : ''; $ticket_url = $e->getProperty('X-TICKETS-URL'); $ticket_url = $ticket_url ? $ticket_url[1] : ''; // =============================== // = Contact name, phone, e-mail = // =============================== $organizer = $e->getProperty('organizer'); if ('MAILTO:' === substr($organizer, 0, 7) && false === strpos($organizer, '@')) { $organizer = substr($organizer, 7); } $contact = $e->getProperty('contact'); $elements = explode(';', $contact, 4); foreach ($elements as $el) { $el = trim($el); // Detect e-mail address. if (false !== strpos($el, '@')) { $data['contact_email'] = $el; } elseif (false !== strpos($el, '://')) { $data['contact_url'] = $this->_parse_legacy_loggable_url($el); } elseif (preg_match('/\\d/', $el)) { $data['contact_phone'] = $el; } else { $data['contact_name'] = $el; } } if (!isset($data['contact_name']) || !$data['contact_name']) { // If no contact name, default to organizer property. $data['contact_name'] = $organizer; } // Store yet-unsaved values to the $data array. $data += array('recurrence_rules' => $rrule, 'exception_rules' => $exrule, 'recurrence_dates' => $rdate, 'exception_dates' => $exdate, 'venue' => $venue, 'address' => $address, 'cost' => $cost, 'ticket_url' => $this->_parse_legacy_loggable_url($ticket_url), 'show_map' => $do_show_map, 'ical_feed_url' => $feed->feed_url, 'ical_source_url' => $e->getProperty('url'), 'ical_organizer' => $organizer, 'ical_contact' => $contact, 'ical_uid' => $e->getProperty('uid'), 'categories' => array_keys($imported_cat), 'tags' => array_keys($imported_tags), 'feed' => $feed, 'post' => array('post_status' => 'publish', 'comment_status' => $comment_status, 'post_type' => AI1EC_POST_TYPE, 'post_author' => 1, 'post_title' => $e->getProperty('summary'), 'post_content' => stripslashes(str_replace('\\n', "\n", $e->getProperty('description'))))); // Create event object. $event = $this->_registry->get('model.event', $data); $recurrence = $event->get('recurrence_rules'); $search = $this->_registry->get('model.search'); // first let's check by UID $matching_event_id = $search->get_matching_event_by_uid_and_url($event->get('ical_uid'), $event->get('ical_feed_url')); // if no result, perform the legacy check. if (null === $matching_event_id) { $matching_event_id = $search->get_matching_event_id($event->get('ical_uid'), $event->get('ical_feed_url'), $event->get('start'), !empty($recurrence)); } if (null === $matching_event_id) { // ================================================= // = Event was not found, so store it and the post = // ================================================= $event->save(); } else { // ====================================================== // = Event was found, let's store the new event details = // ====================================================== // Update the post $post = get_post($matching_event_id); if (null !== $post) { $post->post_title = $event->get('post')->post_title; $post->post_content = $event->get('post')->post_content; wp_update_post($post); // Update the event $event->set('post_id', $matching_event_id); $event->set('post', $post); $event->save(true); } } // if the event was already present , unset it from the array so it's not deleted unset($events_in_db[$event->get('post_id')]); $count++; } return array('count' => $count, 'events_to_delete' => $events_in_db); }
public function get_json() { $event_json = array(); $filters = $this->in->exists('filters', 'int') ? $this->in->getArray('filters', 'int') : false; $range_start = $this->time->fromformat($this->in->get('start', ''), 'Y-m-d'); $range_end = $this->time->fromformat($this->in->get('end', ''), 'Y-m-d'); $filterby = $this->in->get('filterby', 'all'); // parse the feeds $feeds = $this->pdh->get('calendars', 'idlist', array('feed', $filters)); if (is_array($feeds) && count($feeds) > 0) { foreach ($feeds as $feed) { $feedurl = $this->pdh->get('calendars', 'feed', array($feed)); if (isValidURL($feedurl)) { require_once $this->root_path . 'libraries/icalcreator/iCalcreator.class.php'; $vcalendar = new vcalendar(array('url' => $feedurl)); if (TRUE === $vcalendar->parse()) { $vcalendar->sort(); while ($comp = $vcalendar->getComponent('vevent')) { $startdate = $comp->getProperty('dtstart', 1); $enddate = $comp->getProperty('dtend', 1); // set the date for the events $allday = isset($enddate['hour']) && isset($startdate['hour']) ? false : true; if ($allday) { $startdate_out = sprintf("%04d", $startdate['year']) . '-' . sprintf("%02d", $startdate['month']) . '-' . sprintf("%02d", $startdate['day']) . ' 00:00'; $enddate_out = sprintf("%04d", $enddate['year']) . '-' . sprintf("%02d", $enddate['month']) . '-' . sprintf("%02d", $enddate['day'] - 1) . ' 00:00'; } else { $startdate_out = sprintf("%04d", $startdate['year']) . '-' . sprintf("%02d", $startdate['month']) . '-' . sprintf("%02d", $startdate['day']) . ' ' . (isset($startdate['hour']) ? sprintf("%02d", $startdate['hour']) . ':' . sprintf("%02d", $startdate['min']) : '00:00'); $enddate_out = sprintf("%04d", $enddate['year']) . '-' . $enddate['month'] . '-' . $enddate['day'] . ' ' . (isset($enddate['hour']) ? $enddate['hour'] . ':' . $enddate['min'] : '00:00'); } // build the event colours $eventcolor = $this->pdh->get('calendars', 'color', $feed); $eventcolor_txt = get_brightness($eventcolor) > 130 ? 'black' : 'white'; $event_json[] = array('eventid' => $calid, 'title' => $comp->getProperty('summary', 1), 'start' => $startdate_out, 'end' => $enddate_out, 'allDay' => $allday, 'note' => $comp->getProperty('description', 1), 'color' => $eventcolor . ' !important', 'textColor' => $eventcolor_txt . ' !important'); } } } } } // add the calendar events to the json feed $calendars = $this->pdh->get('calendars', 'idlist', array('nofeed', $filters)); $caleventids = $this->pdh->get('calendar_events', 'id_list', array(false, $range_start, $range_end, false, $filterby)); if (is_array($caleventids) && count($caleventids) > 0) { foreach ($caleventids as $calid) { $eventextension = $this->pdh->get('calendar_events', 'extension', array($calid)); $raidmode = $eventextension['calendarmode']; $eventcolor = $this->pdh->get('calendars', 'color', $this->pdh->get('calendar_events', 'calendar_id', array($calid))); $eventcolor_txt = get_brightness($eventcolor) > 130 ? 'black' : 'white'; if (in_array($this->pdh->get('calendar_events', 'calendar_id', array($calid)), $calendars)) { if ($raidmode == 'raid') { // fetch the attendees $attendees_raw = $this->pdh->get('calendar_raids_attendees', 'attendees', array($calid)); $attendees = array(); if (is_array($attendees_raw)) { foreach ($attendees_raw as $attendeeid => $attendeerow) { $attendees[$attendeerow['signup_status']][$attendeeid] = $attendeerow; } } // Build the guest array $guests = array(); if (registry::register('config')->get('calendar_raid_guests') == 1) { $guestarray = registry::register('plus_datahandler')->get('calendar_raids_guests', 'members', array($calid)); if (is_array($guestarray)) { foreach ($guestarray as $guest_row) { $guests[] = $guest_row['name']; } } } // fetch per raid data $raidcal_status = $this->config->get('calendar_raid_status'); $rstatusdata = ''; if (is_array($raidcal_status)) { foreach ($raidcal_status as $raidcalstat_id) { if ($raidcalstat_id != 4) { $actcount = isset($attendees[$raidcalstat_id]) ? count($attendees[$raidcalstat_id]) : 0; if ($raidcalstat_id == 0) { $actcount += is_array($guests) ? count($guests) : 0; } $rstatusdata .= '<div class="raid_status' . $raidcalstat_id . '">' . $this->user->lang(array('raidevent_raid_status', $raidcalstat_id)) . ': ' . $actcount . '</div>'; } } } $rstatusdata .= '<div class="raid_status_total">' . $this->user->lang('raidevent_raid_required') . ': ' . (isset($eventextension) ? $eventextension['attendee_count'] : 0) . '</div>'; $deadlinedate = $this->pdh->get('calendar_events', 'time_start', array($calid)) - $eventextension['deadlinedate'] * 3600; $deadline = $deadlinedate > $this->time->time || $this->config->get('calendar_raid_allowstatuschange') == '1' && $this->pdh->get('calendar_raids_attendees', 'status', array($calid, $this->user->id)) > 0 && $this->pdh->get('calendar_raids_attendees', 'status', array($calid, $this->user->id)) != 4 && $this->pdh->get('calendar_events', 'time_end', array($calid)) > $this->time->time ? false : true; $deadlineflag = $deadline ? '<i class="fa fa-lock fa-lg" title="' . $this->user->lang('raidevent_raid_deadl_reach') . '"></i>' : ''; // Build the JSON $event_json[] = array('type' => 'raid', 'eventid' => $calid, 'editable' => $this->user->check_auth('a_cal_revent_conf', false) || $this->check_permission($calid) ? true : false, 'title' => $this->in->decode_entity($this->pdh->get('calendar_events', 'name', array($calid))), 'url' => $this->routing->build('calendarevent', $this->pdh->get('calendar_events', 'name', array($calid)), $calid), 'start' => $this->time->date('Y-m-d H:i', $this->pdh->get('calendar_events', 'time_start', array($calid))), 'end' => $this->time->date('Y-m-d H:i', $this->pdh->get('calendar_events', 'time_end', array($calid))), 'closed' => $this->pdh->get('calendar_events', 'raidstatus', array($calid)) == 1 ? true : false, 'flag' => $deadlineflag . $this->pdh->get('calendar_raids_attendees', 'html_status', array($calid, $this->user->data['user_id'])), 'icon' => $eventextension['raid_eventid'] ? $this->pdh->get('event', 'icon', array($eventextension['raid_eventid'], true)) : '', 'note' => $this->pdh->get('calendar_events', 'notes', array($calid)), 'raidleader' => $eventextension['raidleader'] > 0 ? implode(', ', $this->pdh->aget('member', 'name', 0, array($eventextension['raidleader']))) : '', 'rstatusdata' => $rstatusdata, 'color' => $eventcolor . ' !important', 'textColor' => $eventcolor_txt . ' !important'); } else { // check if the event is private if (!$this->pdh->get('calendar_events', 'private_userperm', array($calid))) { continue; } $alldayevents = $this->pdh->get('calendar_events', 'allday', array($calid)) > 0 ? true : false; $event_json[] = array('type' => 'event', 'eventid' => $calid, 'editable' => $this->user->check_auth('a_cal_revent_conf', false) || $this->check_permission($calid) ? true : false, 'url' => $this->routing->build('calendarevent', $this->pdh->get('calendar_events', 'name', array($calid)), $calid) . 'eventdetails', 'title' => $this->pdh->get('calendar_events', 'name', array($calid)), 'start' => $this->time->date('Y-m-d H:i', $this->pdh->get('calendar_events', 'time_start', array($calid))), 'end' => $this->time->date('Y-m-d H:i', $this->pdh->get('calendar_events', 'time_end', array($calid, $alldayevents))), 'allDay' => $alldayevents, 'note' => $this->pdh->get('calendar_events', 'notes', array($calid)), 'color' => $eventcolor, 'textColor' => $eventcolor_txt, 'isowner' => $this->pdh->get('calendar_events', 'is_owner', array($calid)), 'isinvited' => $this->pdh->get('calendar_events', 'is_invited', array($calid)), 'joinedevent' => $this->pdh->get('calendar_events', 'joined_invitation', array($calid)), 'author' => $this->pdh->get('calendar_events', 'creator', array($calid)), 'attendees' => $this->pdh->get('calendar_events', 'sharedevent_attendees', array($calid))); } } } } // birthday calendar if ($this->config->get('calendar_show_birthday') && $this->user->check_auth('u_userlist', false)) { $birthday_y = $this->time->date('Y', $range_end); $birthdays = $this->pdh->get('user', 'birthday_list'); if (is_array($birthdays)) { foreach ($birthdays as $birthday_uid => $birthday_ts) { $birthday_month = $this->time->date('m', $birthday_ts); if ($birthday_month >= $this->time->date('m', $range_start) && $birthday_month <= $this->time->date('m', $range_end)) { $event_json[] = array('type' => 'birthday', 'className' => 'cal_birthday', 'title' => $this->pdh->get('user', 'name', array($birthday_uid)), 'start' => $birthday_y . '-' . $this->time->date('m-d', $birthday_ts), 'end' => $birthday_y . '-' . $this->time->date('m-d', $birthday_ts), 'allDay' => true, 'textColor' => '#000000', 'backgroundColor' => '#E8E8E8', 'borderColor' => '#7F7F7F'); } } } } // hooks if ($this->hooks->isRegistered('calendar')) { $arrHooksData = $this->hooks->process('calendar', array('start' => $range_start, 'end' => $range_end), false); if (count($arrHooksData) > 0) { $event_json = array_merge($arrHooksData, $event_json); } } // Output the array as JSON echo json_encode($event_json); exit; }
/** * Expands a list of resources to repeated events, depending on * recurrence rules and recurrence exceptions/modifications * * @param array() $resources Resources returned by GetEvents * @param int $start Start timestamp * @param int $end End timestamp * @param string $calendar Current calendar */ function expand_and_parse_events($resources, $start, $end, $calendar) { $result = array(); // Dates $utc = $this->CI->timezonemanager->getTz('UTC'); $date_start = new DateTime($start, $utc); $date_end = new DateTime($end, $utc); foreach ($resources as $r) { $event_href = $r['href']; $event_etag = $r['etag']; $ical = new vcalendar($this->config); $res = $ical->parse($r['data']); if ($res === FALSE) { $this->CI->extended_logs->message('ERROR', "Couldn't parse event with href=" . $calendar . '/' . $event_href); } $ical->sort(); $timezones = $this->get_timezones($ical); $sy = intval($date_start->format('Y')); $sm = intval($date_start->format('m')); $sd = intval($date_start->format('d')); $ey = intval($date_end->format('Y')); $em = intval($date_end->format('m')); $ed = intval($date_end->format('d')); /* log_message('INTERNALS', 'Pidiendo expansión para ' . $sy . '-' . $sm . '-' . $sd . ' a ' . $ey . '-' . $em . '-' . $ed); log_message('INTERNALS', $event_href); log_message('INTERNALS', $r['data']); */ $expand = $ical->selectComponents($sy, $sm, $sd, $ey, $em, $ed, 'vevent', false, true, false); if ($expand !== FALSE) { foreach ($expand as $year => $year_arr) { foreach ($year_arr as $month => $month_arr) { foreach ($month_arr as $day => $day_arr) { foreach ($day_arr as $event) { $tz = $this->detect_tz($event, $timezones); $result[] = $this->parse_vevent_fullcalendar($event, $event_href, $event_etag, $calendar, $tz, $timezones); } } } } } else { $expand = $ical->selectComponents($sy, $sm, $sd, $ey, $em, $ed, 'vevent', true, true, false); if ($expand === FALSE) { $this->CI->extended_logs->message('ERROR', "Server sent an event which doesn't fit in our dates interval"); } else { foreach ($expand as $event) { $tz = $this->detect_tz($event, $timezones); $result[] = $this->parse_vevent_fullcalendar($event, $event_href, $event_etag, $calendar, $tz, $timezones); } } } } return $result; }
public function get_json() { $event_json = array(); $filters = $this->in->exists('filters', 'int') ? $this->in->getArray('filters', 'int') : false; // parse the feeds $feeds = $this->pdh->get('calendars', 'idlist', array('feed', $filters)); if (is_array($feeds) && count($feeds) > 0) { foreach ($feeds as $feed) { $feedurl = $this->pdh->get('calendars', 'feed', array($feed)); if (isValidURL($feedurl)) { require_once $this->root_path . 'libraries/icalcreator/iCalcreator.class.php'; $vcalendar = new vcalendar(array('url' => $feedurl)); if (TRUE === $vcalendar->parse()) { $vcalendar->sort(); while ($comp = $vcalendar->getComponent('vevent')) { $startdate = $comp->getProperty('dtstart', 1); $enddate = $comp->getProperty('dtend', 1); $startdate_out = $startdate['year'] . '-' . $startdate['month'] . '-' . $startdate['day'] . ' ' . (isset($startdate['hour']) ? $startdate['hour'] . ':' . $startdate['min'] : '00:00'); $enddate_out = $enddate['year'] . '-' . $enddate['month'] . '-' . $enddate['day'] . ' ' . (isset($enddate['hour']) ? $enddate['hour'] . ':' . $enddate['min'] : '00:00'); $allday = isset($enddate['hour']) && isset($startdate['hour']) ? false : true; $eventcolor = $this->pdh->get('calendars', 'color', $feed); $eventcolor_txt = get_brightness($eventcolor) > 130 ? 'black' : 'white'; $event_json[] = array('eventid' => $calid, 'title' => $comp->getProperty('summary', 1), 'start' => $startdate_out, 'end' => $enddate_out, 'allDay' => $allday, 'note' => $comp->getProperty('description', 1), 'color' => '#' . $eventcolor, 'textColor' => $eventcolor_txt); } } } } } // add the calendar events to the json feed $calendars = $this->pdh->get('calendars', 'idlist', array('nofeed', $filters)); $caleventids = $this->pdh->get('calendar_events', 'id_list', array(false, $this->in->get('start', 0), $this->in->get('end', 0))); if (is_array($caleventids) && count($caleventids) > 0) { foreach ($caleventids as $calid) { $eventextension = $this->pdh->get('calendar_events', 'extension', array($calid)); $raidmode = $eventextension['calendarmode']; $eventcolor = $this->pdh->get('calendars', 'color', $this->pdh->get('calendar_events', 'calendar_id', array($calid))); $eventcolor_txt = get_brightness($eventcolor) > 130 ? 'black' : 'white'; if (in_array($this->pdh->get('calendar_events', 'calendar_id', array($calid)), $calendars)) { if ($raidmode == 'raid') { // fetch the attendees $attendees_raw = $this->pdh->get('calendar_raids_attendees', 'attendees', array($calid)); $attendees = array(); if (is_array($attendees_raw)) { foreach ($attendees_raw as $attendeeid => $attendeerow) { $attendees[$attendeerow['signup_status']][$attendeeid] = $attendeerow; } } // Build the guest array $guests = array(); if (registry::register('config')->get('calendar_raid_guests') == 1) { $guestarray = registry::register('plus_datahandler')->get('calendar_raids_guests', 'members', array($calid)); if (is_array($guestarray)) { foreach ($guestarray as $guest_row) { $guests[] = $guest_row['name']; } } } // fetch per raid data $raidcal_status = unserialize($this->config->get('calendar_raid_status')); $rstatusdata = ''; if (is_array($raidcal_status)) { foreach ($raidcal_status as $raidcalstat_id) { if ($raidcalstat_id != 4) { $actcount = isset($attendees[$raidcalstat_id]) ? count($attendees[$raidcalstat_id]) : 0; if ($raidcalstat_id == 0) { $actcount += is_array($guests) ? count($guests) : 0; } $rstatusdata .= '<div class="raid_status' . $raidcalstat_id . '">' . $this->user->lang(array('raidevent_raid_status', $raidcalstat_id)) . ': ' . $actcount . '</div>'; } } } $rstatusdata .= '<div class="raid_status_total">' . $this->user->lang('raidevent_raid_required') . ': ' . (isset($eventextension) ? $eventextension['attendee_count'] : 0) . '</div>'; $deadlinedate = $this->pdh->get('calendar_events', 'time_start', array($calid)) - $eventextension['deadlinedate'] * 3600; $deadline = $deadlinedate > $this->time->time || $this->config->get('calendar_raid_allowstatuschange') == '1' && $this->pdh->get('calendar_raids_attendees', 'status', array($calid, $this->user->id)) > 0 && $this->pdh->get('calendar_raids_attendees', 'status', array($calid, $this->user->id)) != 4 && $this->pdh->get('calendar_events', 'time_end', array($calid)) > $this->time->time ? false : true; $deadlineflag = $deadline ? '<img src="' . $this->root_path . 'images/calendar/clock_s.png" alt="Deadline" title="' . $this->user->lang('raidevent_raid_deadl_reach') . '" />' : ''; // Build the JSON $event_json[] = array('title' => $this->in->decode_entity($this->pdh->get('calendar_events', 'name', array($calid))), 'start' => $this->time->date('Y-m-d H:i', $this->pdh->get('calendar_events', 'time_start', array($calid))), 'end' => $this->time->date('Y-m-d H:i', $this->pdh->get('calendar_events', 'time_end', array($calid))), 'closed' => $this->pdh->get('calendar_events', 'raidstatus', array($calid)) == 1 ? true : false, 'editable' => true, 'eventid' => $calid, 'flag' => $deadlineflag . $this->pdh->get('calendar_raids_attendees', 'html_status', array($calid, $this->user->data['user_id'])), 'url' => 'calendar/viewcalraid.php' . $this->SID . '&eventid=' . $calid, 'icon' => $eventextension['raid_eventid'] ? $this->pdh->get('event', 'icon', array($eventextension['raid_eventid'], true, true)) : '', 'note' => $this->pdh->get('calendar_events', 'notes', array($calid)), 'raidleader' => $eventextension['raidleader'] > 0 ? implode(', ', $this->pdh->aget('member', 'name', 0, array($eventextension['raidleader']))) : '', 'rstatusdata' => $rstatusdata, 'color' => '#' . $eventcolor, 'textColor' => $eventcolor_txt); } else { $event_json[] = array('eventid' => $calid, 'title' => $this->pdh->get('calendar_events', 'name', array($calid)), 'start' => $this->time->date('Y-m-d H:i', $this->pdh->get('calendar_events', 'time_start', array($calid))), 'end' => $this->time->date('Y-m-d H:i', $this->pdh->get('calendar_events', 'time_end', array($calid))), 'allDay' => $this->pdh->get('calendar_events', 'allday', array($calid)) > 0 ? true : false, 'note' => $this->pdh->get('calendar_events', 'notes', array($calid)), 'color' => '#' . $eventcolor, 'textColor' => $eventcolor_txt); } } } } // Output the array as JSON echo json_encode($event_json); exit; }
/** * Process vcalendar instance - add events to database. * * @param vcalendar $v Calendar to retrieve data from. * @param array $args Arbitrary arguments map. * * @throws Ai1ec_Parse_Exception * * @internal param stdClass $feed Instance of feed (see Ai1ecIcs plugin). * @internal param string $comment_status WP comment status: 'open' or 'closed'. * @internal param int $do_show_map Map display status (DB boolean: 0 or 1). * * @return int Count of events added to database. */ public function add_vcalendar_events_to_db(vcalendar $v, array $args) { $forced_timezone = null; $feed = isset($args['feed']) ? $args['feed'] : null; $comment_status = isset($args['comment_status']) ? $args['comment_status'] : 'open'; $do_show_map = isset($args['do_show_map']) ? $args['do_show_map'] : 0; $count = 0; $events_in_db = isset($args['events_in_db']) ? $args['events_in_db'] : 0; //sort by event date function _cmpfcn of iCalcreator.class.php $v->sort(); // Reverse the sort order, so that RECURRENCE-IDs are listed before the // defining recurrence events, and therefore take precedence during // caching. $v->components = array_reverse($v->components); // TODO: select only VEVENT components that occur after, say, 1 month ago. // Maybe use $v->selectComponents(), which takes into account recurrence // Fetch default timezone in case individual properties don't define it $tz = $v->getComponent('vtimezone'); $local_timezone = $this->_registry->get('date.timezone')->get_default_timezone(); $timezone = $local_timezone; if (!empty($tz)) { $timezone = $tz->getProperty('TZID'); } $feed_name = $v->getProperty('X-WR-CALNAME'); $x_wr_timezone = $v->getProperty('X-WR-TIMEZONE'); if (isset($x_wr_timezone[1]) && is_array($x_wr_timezone)) { $forced_timezone = (string) $x_wr_timezone[1]; $timezone = $forced_timezone; } $messages = array(); if (empty($forced_timezone)) { $forced_timezone = $local_timezone; } $current_timestamp = $this->_registry->get('date.time')->format_to_gmt(); // initialize empty custom exclusions structure $exclusions = array(); // go over each event while ($e = $v->getComponent('vevent')) { // Event data array. $data = array(); // ===================== // = Start & end times = // ===================== $start = $e->getProperty('dtstart', 1, true); $end = $e->getProperty('dtend', 1, true); // For cases where a "VEVENT" calendar component // specifies a "DTSTART" property with a DATE value type but none // of "DTEND" nor "DURATION" property, the event duration is taken to // be one day. For cases where a "VEVENT" calendar component // specifies a "DTSTART" property with a DATE-TIME value type but no // "DTEND" property, the event ends on the same calendar date and // time of day specified by the "DTSTART" property. if (empty($end)) { // #1 if duration is present, assign it to end time $end = $e->getProperty('duration', 1, true, true); if (empty($end)) { // #2 if only DATE value is set for start, set duration to 1 day if (!isset($start['value']['hour'])) { $end = array('value' => array('year' => $start['value']['year'], 'month' => $start['value']['month'], 'day' => $start['value']['day'] + 1, 'hour' => 0, 'min' => 0, 'sec' => 0)); if (isset($start['value']['tz'])) { $end['value']['tz'] = $start['value']['tz']; } } else { // #3 set end date to start time $end = $start; } } } $categories = $e->getProperty("CATEGORIES", false, true); $imported_cat = array(Ai1ec_Event_Taxonomy::CATEGORIES => array()); // If the user chose to preserve taxonomies during import, add categories. if ($categories && $feed->keep_tags_categories) { $imported_cat = $this->add_categories_and_tags($categories['value'], $imported_cat, false, true); } $feed_categories = $feed->feed_category; if (!empty($feed_categories)) { $imported_cat = $this->add_categories_and_tags($feed_categories, $imported_cat, false, false); } $tags = $e->getProperty("X-TAGS", false, true); $imported_tags = array(Ai1ec_Event_Taxonomy::TAGS => array()); // If the user chose to preserve taxonomies during import, add tags. if ($tags && $feed->keep_tags_categories) { $imported_tags = $this->add_categories_and_tags($tags[1]['value'], $imported_tags, true, true); } $feed_tags = $feed->feed_tags; if (!empty($feed_tags)) { $imported_tags = $this->add_categories_and_tags($feed_tags, $imported_tags, true, true); } // Event is all-day if no time components are defined $allday = $this->_is_timeless($start['value']) && $this->_is_timeless($end['value']); // Also check the proprietary MS all-day field. $ms_allday = $e->getProperty('X-MICROSOFT-CDO-ALLDAYEVENT'); if (!empty($ms_allday) && $ms_allday[1] == 'TRUE') { $allday = true; } $event_timezone = $timezone; if ($allday) { $event_timezone = $local_timezone; } $start = $this->_time_array_to_datetime($start, $event_timezone, $feed->import_timezone ? $forced_timezone : null); $end = $this->_time_array_to_datetime($end, $event_timezone, $feed->import_timezone ? $forced_timezone : null); if (false === $start || false === $end) { throw new Ai1ec_Parse_Exception('Failed to parse one or more dates given timezone "' . var_export($event_timezone, true) . '"'); continue; } // If all-day, and start and end times are equal, then this event has // invalid end time (happens sometimes with poorly implemented iCalendar // exports, such as in The Event Calendar), so set end time to 1 day // after start time. if ($allday && $start->format() === $end->format()) { $end->adjust_day(+1); } $data += compact('start', 'end', 'allday'); // ======================================= // = Recurrence rules & recurrence dates = // ======================================= if ($rrule = $e->createRrule()) { $rrule = explode(':', $rrule); $rrule = trim(end($rrule)); } if ($exrule = $e->createExrule()) { $exrule = explode(':', $exrule); $exrule = trim(end($exrule)); } if ($rdate = $e->createRdate()) { $arr = explode('RDATE', $rdate); $matches = null; foreach ($arr as $value) { $arr2 = explode(':', $value); if (2 === count($arr2)) { $matches[] = $arr2[1]; } } if (null !== $matches) { $rdate = implode(',', $matches); unset($matches); unset($arr); } else { $rdate = null; } } // =================== // = Exception dates = // =================== $exdate = ''; if ($exdates = $e->createExdate()) { // We may have two formats: // one exdate with many dates ot more EXDATE rules $exdates = explode('EXDATE', $exdates); $def_timezone = $this->_get_import_timezone($event_timezone); foreach ($exdates as $exd) { if (empty($exd)) { continue; } $exploded = explode(':', $exd); $excpt_timezone = $def_timezone; $excpt_date = null; foreach ($exploded as $particle) { if (';TZID=' === substr($particle, 0, 6)) { $excpt_timezone = substr($particle, 6); } else { $excpt_date = trim($particle); } } $exploded = explode(',', $excpt_date); foreach ($exploded as $particle) { // Google sends YYYYMMDD for all-day excluded events if ($allday && 8 === strlen($particle)) { $particle .= 'T000000Z'; $excpt_timezone = 'UTC'; } $ex_dt = $this->_registry->get('date.time', $particle, $excpt_timezone); if ($ex_dt) { if (isset($exdate[0])) { $exdate .= ','; } $exdate .= $ex_dt->format('Ymd\\THis', $excpt_timezone); } } } } // Add custom exclusions if there any $recurrence_id = $e->getProperty('recurrence-id'); if (false === $recurrence_id && !empty($exclusions[$e->getProperty('uid')])) { if (isset($exdate[0])) { $exdate .= ','; } $exdate .= implode(',', $exclusions[$e->getProperty('uid')]); } // ======================== // = Latitude & longitude = // ======================== $latitude = $longitude = NULL; $geo_tag = $e->getProperty('geo'); if (is_array($geo_tag)) { if (isset($geo_tag['latitude']) && isset($geo_tag['longitude'])) { $latitude = (double) $geo_tag['latitude']; $longitude = (double) $geo_tag['longitude']; } } else { if (!empty($geo_tag) && false !== strpos($geo_tag, ';')) { list($latitude, $longitude) = explode(';', $geo_tag, 2); $latitude = (double) $latitude; $longitude = (double) $longitude; } } unset($geo_tag); if (NULL !== $latitude) { $data += compact('latitude', 'longitude'); // Check the input coordinates checkbox, otherwise lat/long data // is not present on the edit event page $data['show_coordinates'] = 1; } // =================== // = Venue & address = // =================== $address = $venue = ''; $location = $e->getProperty('location'); $matches = array(); // This regexp matches a venue / address in the format // "venue @ address" or "venue - address". preg_match('/\\s*(.*\\S)\\s+[\\-@]\\s+(.*)\\s*/', $location, $matches); // if there is no match, it's not a combined venue + address if (empty($matches)) { // temporary fix for Mac ICS import. Se AIOEC-2187 // and https://github.com/iCalcreator/iCalcreator/issues/13 $location = str_replace('\\n', "\n", $location); // if there is a comma, probably it's an address if (false === strpos($location, ',')) { $venue = $location; } else { $address = $location; } } else { $venue = isset($matches[1]) ? $matches[1] : ''; $address = isset($matches[2]) ? $matches[2] : ''; } // ===================================================== // = Set show map status based on presence of location = // ===================================================== $event_do_show_map = $do_show_map; if (1 === $do_show_map && NULL === $latitude && empty($address)) { $event_do_show_map = 0; } // ================== // = Cost & tickets = // ================== $cost = $e->getProperty('X-COST'); $cost = $cost ? $cost[1] : ''; $ticket_url = $e->getProperty('X-TICKETS-URL'); $ticket_url = $ticket_url ? $ticket_url[1] : ''; // =============================== // = Contact name, phone, e-mail = // =============================== $organizer = $e->getProperty('organizer'); if ('MAILTO:' === substr($organizer, 0, 7) && false === strpos($organizer, '@')) { $organizer = substr($organizer, 7); } $contact = $e->getProperty('contact'); $elements = explode(';', $contact, 4); foreach ($elements as $el) { $el = trim($el); // Detect e-mail address. if (false !== strpos($el, '@')) { $data['contact_email'] = $el; } elseif (false !== strpos($el, '://')) { $data['contact_url'] = $el; } elseif (preg_match('/\\d/', $el)) { $data['contact_phone'] = $el; } else { $data['contact_name'] = $el; } } if (!isset($data['contact_name']) || !$data['contact_name']) { // If no contact name, default to organizer property. $data['contact_name'] = $organizer; } $description = stripslashes(str_replace('\\n', "\n", $e->getProperty('description'))); $description = $this->_remove_ticket_url($description); // Store yet-unsaved values to the $data array. $data += array('recurrence_rules' => $rrule, 'exception_rules' => $exrule, 'recurrence_dates' => $rdate, 'exception_dates' => $exdate, 'venue' => $venue, 'address' => $address, 'cost' => $cost, 'ticket_url' => $ticket_url, 'show_map' => $event_do_show_map, 'ical_feed_url' => $feed->feed_url, 'ical_source_url' => $e->getProperty('url'), 'ical_organizer' => $organizer, 'ical_contact' => $contact, 'ical_uid' => $this->_get_ical_uid($e), 'categories' => array_keys($imported_cat[Ai1ec_Event_Taxonomy::CATEGORIES]), 'tags' => array_keys($imported_tags[Ai1ec_Event_Taxonomy::TAGS]), 'feed' => $feed, 'post' => array('post_status' => 'publish', 'comment_status' => $comment_status, 'post_type' => AI1EC_POST_TYPE, 'post_author' => 1, 'post_title' => $e->getProperty('summary'), 'post_content' => $description)); // register any custom exclusions for given event $exclusions = $this->_add_recurring_events_exclusions($e, $exclusions, $start); // Create event object. $data = apply_filters('ai1ec_pre_init_event_from_feed', $data, $e, $feed); $event = $this->_registry->get('model.event', $data); // Instant Event $is_instant = $e->getProperty('X-INSTANT-EVENT'); if ($is_instant) { $event->set_no_end_time(); } $recurrence = $event->get('recurrence_rules'); $search = $this->_registry->get('model.search'); // first let's check by UID $matching_event_id = $search->get_matching_event_by_uid_and_url($event->get('ical_uid'), $event->get('ical_feed_url')); // if no result, perform the legacy check. if (null === $matching_event_id) { $matching_event_id = $search->get_matching_event_id($event->get('ical_uid'), $event->get('ical_feed_url'), $event->get('start'), !empty($recurrence)); } if (null === $matching_event_id) { // ================================================= // = Event was not found, so store it and the post = // ================================================= $event->save(); $count++; } else { // ====================================================== // = Event was found, let's store the new event details = // ====================================================== $uid_cal = $e->getProperty('uid'); if (!ai1ec_is_blank($uid_cal)) { $uid_cal_original = sprintf($event->get_uid_pattern(), $matching_event_id); if ($uid_cal_original === $uid_cal) { //avoiding cycle import //ignore the event, it belongs to site unset($events_in_db[$matching_event_id]); continue; } } // Update the post $post = get_post($matching_event_id); if (null !== $post) { $post->post_title = $event->get('post')->post_title; $post->post_content = $event->get('post')->post_content; wp_update_post($post); // Update the event $event->set('post_id', $matching_event_id); $event->set('post', $post); $event->save(true); $count++; } } do_action('ai1ec_ics_event_saved', $event, $feed); // import not standard taxonomies. unset($imported_cat[Ai1ec_Event_Taxonomy::CATEGORIES]); foreach ($imported_cat as $tax_name => $ids) { wp_set_post_terms($event->get('post_id'), array_keys($ids), $tax_name); } unset($imported_tags[Ai1ec_Event_Taxonomy::TAGS]); foreach ($imported_tags as $tax_name => $ids) { wp_set_post_terms($event->get('post_id'), array_keys($ids), $tax_name); } // import the metadata used by ticket events $cost_type = $e->getProperty('X-COST-TYPE'); if ($cost_type && false === ai1ec_is_blank($cost_type[1])) { update_post_meta($event->get('post_id'), '_ai1ec_cost_type', $cost_type[1]); } $api_event_id = $e->getProperty('X-API-EVENT-ID'); if ($api_event_id && false === ai1ec_is_blank($api_event_id[1])) { $api_event_id = $api_event_id[1]; } else { $api_event_id = null; } $api_url = $e->getProperty('X-API-URL'); if ($api_url && false === ai1ec_is_blank($api_url[1])) { $api_url = $api_url[1]; } else { $api_url = null; } $checkout_url = $e->getProperty('X-CHECKOUT-URL'); if ($checkout_url && false === ai1ec_is_blank($checkout_url[1])) { $checkout_url = $checkout_url[1]; } else { $checkout_url = null; } $currency = $e->getProperty('X-API-EVENT-CURRENCY'); if ($currency && false === ai1ec_is_blank($currency[1])) { $currency = $currency[1]; } else { $currency = null; } if ($api_event_id || $api_url || $checkout_url || $currency) { if (!isset($api)) { $api = $this->_registry->get('model.api.api-ticketing'); } $api->save_api_event_data($event->get('post_id'), $api_event_id, $api_url, $checkout_url, $currency); } $wp_images_url = $e->getProperty('X-WP-IMAGES-URL'); if ($wp_images_url && false === ai1ec_is_blank($wp_images_url[1])) { $images_arr = explode(',', $wp_images_url[1]); foreach ($images_arr as $key => $value) { $images_arr[$key] = explode(';', $value); } if (count($images_arr) > 0) { update_post_meta($event->get('post_id'), '_featured_image', $images_arr); } } unset($events_in_db[$event->get('post_id')]); } //close while iteration return array('count' => $count, 'events_to_delete' => $events_in_db, 'messages' => $messages, 'name' => $feed_name); }
function GetMessage($folderid, $id, $truncsize, $mimesupport = 0) { debugLog('CalDAV::GetMessage(' . $folderid . ', ' . $id . ', ..)'); if (trim($id) == "") { return; } if ($folderid == "calendar") { $output = $this->_events[$id]['data']; } elseif ($folderid == "tasks") { $output = $this->_tasks[$id]['data']; } else { return; } //debugLog("CalDAV::Got File ".$id." now parseing ".$output); $v = new vcalendar(); $v->runparse($output); $v->sort(); if ($folderid == "tasks") { while ($vtodo = $v->getComponent('vtodo', $vcounter)) { $message = $this->converttotask($vtodo, $truncsize); $vcounter++; } } else { $vcounter = 1; $fullexceptionsarray = array(); while ($vevent = $v->getComponent('vevent', $vcounter)) { $val = $vevent->getProperty("RECURRENCE-ID"); if ($val === false) { $message = $this->converttoappointment($vevent, $truncsize); } else { $tmp = $this->converttoappointment($vevent, $truncsize); $tmp->deleted = "0"; //The exceptionstarttime is the ORIGINAL starttime of the event //On Thunderbird this is equal to the RECCURENCE-ID (which is in $val) $tmp->exceptionstarttime = mktime($val['hour'], $val['min'], $val['sec'], $val['month'], $val['day'], $val['year']); unset($tmp->uid); unset($tmp->exceptions); array_push($fullexceptionsarray, $tmp); unset($tmp); } $vcounter++; } $message->exceptions = array_merge($message->exceptions, $fullexceptionsarray); } if ($vtimezone = $v->getComponent('vtimezone')) { $message = $this->setoutlooktimezone($message, $vtimezone); } debugLog("CalDAV::Finsihed Converting " . $id . " now returning"); return $message; }
/** * @param DBClass_friendica_calendars $calendar * @param DBClass_friendica_calendarobjects $calendarobject */ function renderCalDavEntry_data(&$calendar, &$calendarobject) { $a = get_app(); $v = new vcalendar(); $v->setConfig('unique_id', $a->get_hostname()); $v->parse($calendarobject->calendardata); $v->sort(); $eventArray = $v->selectComponents(2009, 1, 1, date("Y") + 2, 12, 30); $start_min = $end_max = ""; $allday = $summary = $vevent = $rrule = $color = $start = $end = null; $location = $description = ""; foreach ($eventArray as $yearArray) { foreach ($yearArray as $monthArray) { foreach ($monthArray as $day => $dailyEventsArray) { foreach ($dailyEventsArray as $vevent) { /** @var $vevent vevent */ $start = ""; $rrule = "NULL"; $allday = 0; $dtstart = $vevent->getProperty('X-CURRENT-DTSTART'); if (is_array($dtstart)) { $start = "'" . $dtstart[1] . "'"; if (strpos($dtstart[1], ":") === false) { $allday = 1; } } else { $dtstart = $vevent->getProperty('dtstart'); if (isset($dtstart["day"]) && $dtstart["day"] == $day) { // Mehrtägige Events nur einmal rein if (isset($dtstart["hour"])) { $start = "'" . $dtstart["year"] . "-" . $dtstart["month"] . "-" . $dtstart["day"] . " " . $dtstart["hour"] . ":" . $dtstart["minute"] . ":" . $dtstart["secont"] . "'"; } else { $start = "'" . $dtstart["year"] . "-" . $dtstart["month"] . "-" . $dtstart["day"] . " 00:00:00'"; $allday = 1; } } } $dtend = $vevent->getProperty('X-CURRENT-DTEND'); if (is_array($dtend)) { $end = "'" . $dtend[1] . "'"; if (strpos($dtend[1], ":") === false) { $allday = 1; } } else { $dtend = $vevent->getProperty('dtend'); if (isset($dtend["hour"])) { $end = "'" . $dtend["year"] . "-" . $dtend["month"] . "-" . $dtend["day"] . " " . $dtend["hour"] . ":" . $dtend["minute"] . ":" . $dtend["second"] . "'"; } else { $end = "'" . $dtend["year"] . "-" . $dtend["month"] . "-" . $dtend["day"] . " 00:00:00' - INTERVAL 1 SECOND"; $allday = 1; } } $summary = $vevent->getProperty('summary'); $description = $vevent->getProperty('description'); $location = $vevent->getProperty('location'); $rrule_prob = $vevent->getProperty('rrule'); if ($rrule_prob != null) { $rrule = $vevent->createRrule(); $rrule = "'" . dbesc($rrule) . "'"; } $color_ = $vevent->getProperty("X-ANIMEXX-COLOR"); $color = is_array($color_) ? $color_[1] : "NULL"; if ($start_min == "" || preg_replace("/[^0-9]/", "", $start) < preg_replace("/[^0-9]/", "", $start_min)) { $start_min = $start; } if ($end_max == "" || preg_replace("/[^0-9]/", "", $end) > preg_replace("/[^0-9]/", "", $start_min)) { $end_max = $end; } } } } } if ($start_min != "") { if ($allday && mb_strlen($end_max) == 12) { $x = explode("-", str_replace("'", "", $end_max)); $time = mktime(0, 0, 0, IntVal($x[1]), IntVal($x[2]), IntVal($x[0])); $end_max = date("'Y-m-d H:i:s'", $time - 1); } q("INSERT INTO %s%sjqcalendar (`uid`, `namespace`, `namespace_id`, `ical_uri`, `Subject`, `Location`, `Description`, `StartTime`, `EndTime`, `IsAllDayEvent`, `RecurringRule`, `Color`)\n\t\t\tVALUES (%d, %d, %d, '%s', '%s', '%s', '%s', %s, %s, %d, '%s', '%s')", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendar->uid), IntVal($calendarobject->namespace), IntVal($calendarobject->namespace_id), dbesc($calendarobject->uri), dbesc($summary), dbesc($location), dbesc(str_replace("\\n", "\n", $description)), $start_min, $end_max, IntVal($allday), dbesc($rrule), dbesc($color)); foreach ($vevent->components as $comp) { /** @var $comp calendarComponent */ $trigger = $comp->getProperty("TRIGGER"); $sql_field = $trigger["relatedStart"] ? $start : $end; $sql_op = $trigger["before"] ? "DATE_SUB" : "DATE_ADD"; $num = ""; $rel_type = ""; $rel_value = 0; if (isset($trigger["second"])) { $num = IntVal($trigger["second"]) . " SECOND"; $rel_type = "second"; $rel_value = IntVal($trigger["second"]); } if (isset($trigger["minute"])) { $num = IntVal($trigger["minute"]) . " MINUTE"; $rel_type = "minute"; $rel_value = IntVal($trigger["minute"]); } if (isset($trigger["hour"])) { $num = IntVal($trigger["hour"]) . " HOUR"; $rel_type = "hour"; $rel_value = IntVal($trigger["hour"]); } if (isset($trigger["day"])) { $num = IntVal($trigger["day"]) . " DAY"; $rel_type = "day"; $rel_value = IntVal($trigger["day"]); } if (isset($trigger["week"])) { $num = IntVal($trigger["week"]) . " WEEK"; $rel_type = "week"; $rel_value = IntVal($trigger["week"]); } if (isset($trigger["month"])) { $num = IntVal($trigger["month"]) . " MONTH"; $rel_type = "month"; $rel_value = IntVal($trigger["month"]); } if (isset($trigger["year"])) { $num = IntVal($trigger["year"]) . " YEAR"; $rel_type = "year"; $rel_value = IntVal($trigger["year"]); } if ($trigger["before"]) { $rel_value *= -1; } if ($rel_type != "") { $not_date = "{$sql_op}({$sql_field}, INTERVAL {$num})"; q("INSERT INTO %s%snotifications (`uid`, `ical_uri`, `rel_type`, `rel_value`, `alert_date`, `notified`) VALUES ('%s', '%s', '%s', '%s', %s, IF(%s < NOW(), 1, 0))", CALDAV_SQL_DB, CALDAV_SQL_PREFIX, IntVal($calendar->uid), dbesc($calendarobject->uri), dbesc($rel_type), IntVal($rel_value), $not_date, $not_date); } } } }
/** * add_vcalendar_events_to_db method * * Process vcalendar instance - add events to database * * @param vcalendar $v Calendar to retrieve data from * @param stdClass $feed Instance of feed (see Ai1ecIcs plugin) * @param string $comment_status WP comment status: 'open' or 'closed' * @param int $do_show_map Map display status (DB boolean: 0 or 1) * * @return int Count of events added to database */ public function add_vcalendar_events_to_db(vcalendar $v, $feed, $comment_status, $do_show_map = 0) { global $ai1ec_events_helper; $count = 0; $do_show_map = Ai1ec_Number_Utility::db_bool($do_show_map); $v->sort(); // Reverse the sort order, so that RECURRENCE-IDs are listed before the // defining recurrence events, and therefore take precedence during // caching. $v->components = array_reverse($v->components); // TODO: select only VEVENT components that occur after, say, 1 month ago. // Maybe use $v->selectComponents(), which takes into account recurrence // Fetch default timezone in case individual properties don't define it $timezone = $v->getProperty('X-WR-TIMEZONE'); $timezone = $timezone[1]; // go over each event while ($e = $v->getComponent('vevent')) { // Event data array. $data = array(); // ===================== // = Start & end times = // ===================== $start = $e->getProperty('dtstart', 1, true); $end = $e->getProperty('dtend', 1, true); // For cases where a "VEVENT" calendar component // specifies a "DTSTART" property with a DATE value type but no // "DTEND" nor "DURATION" property, the event's duration is taken to // be one day. For cases where a "VEVENT" calendar component // specifies a "DTSTART" property with a DATE-TIME value type but no // "DTEND" property, the event ends on the same calendar date and // time of day specified by the "DTSTART" property. if (empty($end)) { // #1 if duration is present, assign it to end time $end = $e->getProperty('duration', 1, true, true); if (empty($end)) { // #2 if only DATE value is set for start, set duration to 1 day if (!isset($start['value']['hour'])) { $end = array('year' => $start['value']['year'], 'month' => $start['value']['month'], 'day' => $start['value']['day'] + 1, 'hour' => 0, 'min' => 0, 'sec' => 0, 'tz' => $start['value']['tz']); } else { // #3 set end date to start time $end = $start; } } } // Event is all-day if no time components are defined $allday = !isset($start['value']['hour']); // Also check the proprietary MS all-day field. $ms_allday = $e->getProperty('X-MICROSOFT-CDO-ALLDAYEVENT'); if (!empty($ms_allday) && $ms_allday[1] == 'TRUE') { $allday = true; } // convert times to GMT UNIX timestamps $start = $this->time_array_to_timestamp($start, $timezone); $end = $this->time_array_to_timestamp($end, $timezone); if (false === $start || false === $end) { trigger_error('Failed to parse one or more dates given timezone "' . var_export($timezone, true) . '".', E_USER_WARNING); continue; } // If all-day, and start and end times are equal, then this event has // invalid end time (happens sometimes with poorly implemented iCalendar // exports, such as in The Event Calendar), so set end time to 1 day // after start time. if ($allday && $start === $end) { $end += 24 * 60 * 60; } // Due to potential time zone differences (WP time zone vs. feed time // zone), must set all-day event start/end dates to midnight of the // respective days, in the *local* time zone. if ($allday) { $start = $ai1ec_events_helper->gmgetdate($start); $start = gmmktime(0, 0, 0, $start['mon'], $start['mday'], $start['year']); $start = $ai1ec_events_helper->local_to_gmt($start); $end = $ai1ec_events_helper->gmgetdate($end); $end = gmmktime(0, 0, 0, $end['mon'], $end['mday'], $end['year']); $end = $ai1ec_events_helper->local_to_gmt($end); } $data += compact('start', 'end', 'allday'); // ======================================= // = Recurrence rules & recurrence dates = // ======================================= if ($rrule = $e->createRrule()) { $rrule = trim(end(explode(':', $rrule))); } if ($exrule = $e->createExrule()) { $exrule = trim(end(explode(':', $exrule))); } if ($rdate = $e->createRdate()) { $rdate = trim(end(explode(':', $rdate))); } // =================== // = Exception dates = // =================== $exdate_array = array(); if ($exdates = $e->createExdate()) { // We may have two formats: // one exdate with many dates ot more EXDATE rules $exdates = explode("EXDATE", $exdates); foreach ($exdates as $exd) { if (empty($exd)) { continue; } $exdate_array[] = trim(end(explode(':', $exd))); } } // This is the local string. $exdate_loc = implode(',', $exdate_array); $gmt_exdates = array(); // Now we convert the string to gmt. I must do it here // because EXDATE:date1,date2,date3 must be parsed if (!empty($exdate_loc)) { foreach (explode(',', $exdate_loc) as $date) { // If the date is > 8 char that's a datetime, we just want the // date part for the exclusion rules if (strlen($date) > 8) { $date = substr($date, 0, 8); } $gmt_exdates[] = $ai1ec_events_helper->exception_dates_to_gmt($date); } } $exdate = implode(',', $gmt_exdates); // ======================== // = Latitude & longitude = // ======================== $latitude = $longitude = NULL; $geo_tag = $e->getProperty('geo'); if (!empty($geo_tag) && false !== strpos(';', $geo_tag)) { list($latitude, $longitude) = explode(';', $geo_tag, 2); $latitude = (double) $latitude; $longitude = (double) $longitude; } unset($geo_tag); if (NULL !== $latitude) { $data += compact('latitude', 'longitude'); } // =================== // = Venue & address = // =================== $address = $venue = ''; $location = $e->getProperty('location'); $matches = array(); // This regexp matches a venue / address in the format // "venue @ address" or "venue - address". preg_match('/\\s*(.*\\S)\\s+[\\-@]\\s+(.*)\\s*/', $location, $matches); // if there is no match, it's not a combined venue + address if (empty($matches)) { // if there is a comma, probably it's an address if (false === strpos($location, ',')) { $venue = $location; } else { $address = $location; } } else { $venue = isset($matches[1]) ? $matches[1] : ''; $address = isset($matches[2]) ? $matches[2] : ''; } // ===================================================== // = Set show map status based on presence of location = // ===================================================== if (1 === $do_show_map && NULL === $latitude && empty($address)) { $do_show_map = 0; } // ================== // = Cost & tickets = // ================== $cost = $e->getProperty('X-COST'); $cost = $cost ? $cost[1] : ''; $ticket_url = $e->getProperty('X-TICKETS-URL'); $ticket_url = $ticket_url ? $ticket_url[1] : ''; // =============================== // = Contact name, phone, e-mail = // =============================== $organizer = $e->getProperty('organizer'); $contact = $e->getProperty('contact'); $elements = explode(';', $contact, 4); foreach ($elements as $el) { $el = trim($el); // Detect e-mail address. if (false !== strpos($el, '@')) { $data['contact_email'] = $el; } elseif (false !== strpos($el, '://')) { $data['contact_url'] = $el; } elseif (preg_match('/\\d/', $el)) { $data['contact_phone'] = $el; } else { $data['contact_name'] = $el; } } if (!$data['contact_name']) { // If no contact name, default to organizer property. $data['contact_name'] = $organizer; } // Store yet-unsaved values to the $data array. $data += array('recurrence_rules' => $rrule, 'exception_rules' => $exrule, 'recurrence_dates' => $rdate, 'exception_dates' => $exdate, 'venue' => $venue, 'address' => $address, 'cost' => $cost, 'ticket_url' => $ticket_url, 'show_map' => $do_show_map, 'ical_feed_url' => $feed->feed_url, 'ical_source_url' => $e->getProperty('url'), 'ical_organizer' => $organizer, 'ical_contact' => $contact, 'ical_uid' => $e->getProperty('uid'), 'categories' => $feed->feed_category, 'tags' => $feed->feed_tags, 'feed' => $feed, 'post' => array('post_status' => 'publish', 'comment_status' => $comment_status, 'post_type' => AI1EC_POST_TYPE, 'post_author' => 1, 'post_title' => $e->getProperty('summary'), 'post_content' => stripslashes(str_replace('\\n', "\n", $e->getProperty('description'))))); // Create event object. $event = new Ai1ec_Event($data); // TODO: when singular events change their times in an ICS feed from one // import to another, the matching_event_id is null, which is wrong. We // want to match that event that previously had a different time. // However, we also want the function to NOT return a matching event in // the case of recurring events, and different events with different // RECURRENCE-IDs... ponder how to solve this.. may require saving the // RECURRENCE-ID as another field in the database. $matching_event_id = $ai1ec_events_helper->get_matching_event_id($event->ical_uid, $event->ical_feed_url, $event->start, !empty($event->recurrence_rules)); if (is_null($matching_event_id)) { // ================================================= // = Event was not found, so store it and the post = // ================================================= $event->save(); } else { // ====================================================== // = Event was found, let's store the new event details = // ====================================================== // Update the post $post = get_post($matching_event_id); $post->post_title = $event->post->post_title; $post->post_content = $event->post->post_content; wp_update_post($post); // Update the event $event->post_id = $matching_event_id; $event->post = $post; $event->save(true); // Delete event's cache $ai1ec_events_helper->delete_event_cache($matching_event_id); } // Regenerate event's cache $ai1ec_events_helper->cache_event($event); $count++; } return $count; }
function createTestFile() { $dirFile = CALDIR . DIRECTORY_SEPARATOR . TESTFILE; $calendar = new vcalendar(); $calendar->setConfig('unique_id', UNIQUE); if (!$calendar->setConfig('directory', CALDIR)) { addLogEntry(1, ' ERROR (11) when setting directory \'' . CALDIR . '\', check directory/file permissions!!'); return FALSE; } elseif (!$calendar->setConfig('filename', TESTFILE)) { addLogEntry(1, " ERROR (12) when setting directory/file '{$dirFile}', check directory/file permissions!!"); return FALSE; } $calendar->setProperty('METHOD', METHOD); $calendar->setProperty('X-WR-CALNAME', CALNAME); $calendar->setProperty('X-WR-CALDESC', CALDESC); $calendar->setProperty('X-WR-TIMEZONE', TIMEZONE); $date = mktime(0, 0, 0, (int) substr(THISDATE, 4, 2), (int) substr(THISDATE, 6, 2), (int) substr(THISDATE, 0, 4)); $stopDate = $date + 7 * 24 * 3600; $eventCount = 1; // random priority, 1 to 9. HIGH (1-4), MEDIUM (5), LOW (6-9) // reversed prio; HIGH: weight 1, MEDIUM weight 4, LOW: weight 8 $prioArr = array(); for ($r = 1; $r <= 9; $r++) { $weight = 5 < $r ? 1 : 5 > $r ? 8 : 4; for ($r1 = 1; $r1 <= $weight; $r1++) { $prioArr[] = $r; } } mt_srand(); shuffle($prioArr); // array to randomly select a summary from $summaries = array('Duis ac dui sit amet ante auctor euismod.', 'Suspendisse_pellentesque_velit_in_tortor.', 'Mauris vulputate.', 'Nulla sapien pede, dapibus sed.', 'Maecenas tristique, pede_id_sollicitudin_posuere, enim nibh mollis odio.', 'Lorem ipsum dolor sit amet, consectetuerAdipiscingElit.'); while ($date <= $stopDate) { $dayCount = 1; while ($dayCount < 4) { $event = new vevent(); $eventdate = $date + mt_rand(7, 18) * 3600; // random start hour, 7 to 18 $event->setProperty('DTSTART', array('timestamp' => $eventdate)); // random duration, 1-4 hours or 2 days if (9 > mt_rand(1, 9)) { $event->setProperty('DURATION', 0, 0, mt_rand(1, 4)); } else { $event->setProperty('DURATION', 0, 2); } // 2 days duration $event->setProperty('SUMMARY', "Event #{$eventCount}. " . $summaries[mt_rand(0, 5)]); $event->setProperty('CATEGORIES', "Category #{$eventCount}"); $event->setProperty('LOCATION', "Location #{$eventCount}"); $event->setProperty('RESOURCES', "Resource #{$eventCount}"); $event->setProperty('ORGANIZER', "chair.{$eventCount}@" . UNIQUE); $event->setProperty('CONTACT', "contact.{$eventCount}@" . UNIQUE); $event->setProperty('DESCRIPTION', 'Lorem ipsum dolor sit amet, ' . 'consectetuer adipiscing elit. ' . 'Mauris vulputate. Suspendisse ' . 'pellentesque velit in tortor. ' . 'Nulla sapien pede, dapibus sed.'); // random priority, 1 to 9. HIGH (1-4), MEDIUM (5), LOW (6-9) $event->setProperty('PRIORITY', $prioArr[mt_rand(0, count($prioArr) - 1)]); // two attendees every event $event->setProperty('ATTENDEE', 'attendee.' . $eventCount . '.1@' . UNIQUE); $event->setProperty('ATTENDEE', 'attendee.' . $eventCount . '.2@' . UNIQUE); // two comments every event $event->setProperty('COMMENT', 'Duis ac dui sit amet ante auctor euismod. Sed vulputate.'); $event->setProperty('COMMENT', 'Maecenas tristique, pede id sollicitudin posuere, enim nibh mollis odio.'); $event->setProperty('CREATED', array('timestamp' => $eventdate - 2 * 24 * 3600)); // fake two days before event startdate $event->setProperty('LAST-MODIFIED', array('timestamp' => $eventdate - 23 * 3600)); // fake 25 hours before event startdate if (5 > $eventCount) { $event->setProperty('RRULE', array('FREQ' => 'DAILY', 'COUNT' => 4, 'INTERVAL' => 3)); $event->setProperty('EXRULE', array('FREQ' => 'DAILY', 'COUNT' => 2, 'INTERVAL' => 6)); } $event->setProperty('URL', 'http://www.kigkonsult.se/tinycal/index.php'); if (FALSE === $calendar->setComponent($event)) { error_log('setComponent error'); } $eventCount++; $dayCount++; } $date += 24 * 3600; } $calendar->sort(); if (FALSE === $calendar->saveCalendar()) { addLogEntry(1, " ERROR (13) saving calendar file '{$dirFile}'"); } }