mirror of
https://github.com/salesagility/SuiteCRM.git
synced 2024-12-22 12:28:31 +00:00
555 lines
23 KiB
PHP
Executable File
555 lines
23 KiB
PHP
Executable File
<?php
|
|
if (!defined('sugarEntry') || !sugarEntry) {
|
|
die('Not A Valid Entry Point');
|
|
}
|
|
/**
|
|
*
|
|
* SugarCRM Community Edition is a customer relationship management program developed by
|
|
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
|
|
*
|
|
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
|
|
* Copyright (C) 2011 - 2018 SalesAgility Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU Affero General Public License version 3 as published by the
|
|
* Free Software Foundation with the addition of the following permission added
|
|
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
|
|
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
|
|
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License along with
|
|
* this program; if not, see http://www.gnu.org/licenses or write to the Free
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA.
|
|
*
|
|
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
|
|
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of this program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU Affero General Public License version 3.
|
|
*
|
|
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
|
* these Appropriate Legal Notices must retain the display of the "Powered by
|
|
* SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
|
|
* reasonably feasible for technical reasons, the Appropriate Legal Notices must
|
|
* display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
|
|
*/
|
|
|
|
|
|
|
|
require_once('include/utils.php');
|
|
require_once('modules/Calendar/Calendar.php');
|
|
require_once('modules/vCals/vCal.php');
|
|
|
|
/**
|
|
* Class for constructing the iCal response string for the current user.
|
|
*
|
|
* @see vCal
|
|
*/
|
|
#[\AllowDynamicProperties]
|
|
class iCal extends vCal
|
|
{
|
|
public const UTC_FORMAT = 'Ymd\THi00\Z';
|
|
|
|
/**
|
|
* Constructor for the iCal class.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
}
|
|
|
|
/**
|
|
* Gets a UTC formatted string from the given dateTime
|
|
*
|
|
* @param SugarDateTime $dateTime the dateTime to format
|
|
* @return string the UTC formatted dateTime
|
|
*/
|
|
protected function getUtcDateTime($dateTime)
|
|
{
|
|
return $dateTime->format(self::UTC_FORMAT);
|
|
}
|
|
|
|
/**
|
|
* Gets the UTC formatted dateTime from the given timestamp.
|
|
*
|
|
* Checks the version of Sugar to see if user timezone adjustments are needed.
|
|
*
|
|
* @param integer $ts the timestamp to format
|
|
* @return string the UTC formatted dateTime
|
|
*/
|
|
protected function getUtcTime($ts)
|
|
{
|
|
global $timedate, $sugar_version;
|
|
$timestamp = ($ts+(date('Z')-$timedate->adjustmentForUserTimeZone()*60));
|
|
return $this->getUtcDateTime(new SugarDateTime("@" . $ts));
|
|
}
|
|
|
|
/**
|
|
* Converts the given number of minutes to formatted number of hours and remaining minutes.
|
|
*
|
|
* @param integer $minutes the number of minutes to format
|
|
* @return string the formatted hours and minutes
|
|
*/
|
|
protected function convertMinsToHoursAndMins($minutes)
|
|
{
|
|
$hrs = floor(abs($minutes) / 60);
|
|
$remainderMinutes = abs($minutes) - ($hrs * 60);
|
|
$sign = (($minutes < 0) ? "-" : "+");
|
|
return $sign . str_pad($hrs, 2, "0", STR_PAD_LEFT) . str_pad($remainderMinutes, 2, "0", STR_PAD_LEFT);
|
|
}
|
|
|
|
/**
|
|
* Create a todo entry for the given task.
|
|
*
|
|
* @param UserBean $user_bean the current UserBean
|
|
* @param Task $task the task for the todo entry
|
|
* @param string $moduleName the name of the task module
|
|
* @param string $dtstamp the current timestamp
|
|
* @return string the todo entry for the task
|
|
*/
|
|
protected function createSugarIcalTodo($user_bean, $task, $moduleName, $dtstamp)
|
|
{
|
|
global $sugar_config;
|
|
$ical_array = array();
|
|
$ical_array[] = array("BEGIN", "VTODO");
|
|
$validDueDate = (isset($task->date_due) && $task->date_due != "" && $task->date_due != "0000-00-00");
|
|
$validDueTime = (isset($task->time_due) && $task->time_due != "");
|
|
$dueYear = 1970;
|
|
$dueMonth = 1;
|
|
$dueDay = 1;
|
|
$dueHour = 0;
|
|
$dueMin = 0;
|
|
if ($validDueDate) {
|
|
$dateDueArr = explode("-", $task->date_due);
|
|
$dueYear = (int)$dateDueArr[0];
|
|
$dueMonth = (int)$dateDueArr[1];
|
|
$dueDay = (int)$dateDueArr[2];
|
|
|
|
if ($validDueTime) {
|
|
$timeDueArr = explode(":", $task->time_due);
|
|
$dueHour = (int)$timeDueArr[0];
|
|
$dueMin = (int)$timeDueArr[1];
|
|
}
|
|
}
|
|
$date_arr = array(
|
|
'day'=>$dueDay,
|
|
'month'=>$dueMonth,
|
|
'hour'=>$dueHour,
|
|
'min'=>$dueMin,
|
|
'year'=>$dueYear);
|
|
$due_date_time = new SugarDateTime();
|
|
$due_date_time->setDate($dueYear, $dueMonth, $dueDay);
|
|
$due_date_time->setTime($dueHour, $dueMin);
|
|
$ical_array[] = array(
|
|
"DTSTART;TZID=" . $user_bean->getPreference('timezone'),
|
|
str_replace("Z", "", $this->getUtcDateTime($due_date_time))
|
|
);
|
|
$ical_array[] = array("DTSTAMP", $dtstamp);
|
|
$ical_array[] = array("SUMMARY", $task->name);
|
|
$ical_array[] = array("UID", $task->id);
|
|
if ($validDueDate) {
|
|
$iCalDueDate = str_replace("-", "", (string) $task->date_due);
|
|
if (strlen($iCalDueDate) > 8) {
|
|
$iCalDueDate = substr($iCalDueDate, 0, 8);
|
|
}
|
|
$ical_array[] = array("DUE;VALUE=DATE", $iCalDueDate);
|
|
}
|
|
if ($moduleName == "ProjectTask") {
|
|
$ical_array[] = array(
|
|
"DESCRIPTION:Project",
|
|
$task->project_name . vCal::EOL . vCal::EOL . $task->description
|
|
);
|
|
} else {
|
|
$ical_array[] = array("DESCRIPTION", $task->description);
|
|
}
|
|
$ical_array[] = array(
|
|
"URL;VALUE=URI",
|
|
$sugar_config['site_url']."/index.php?module=".$moduleName."&action=DetailView&record=".$task->id
|
|
);
|
|
if ($task->status == 'Completed') {
|
|
$ical_array[] = array("STATUS", "COMPLETED");
|
|
$ical_array[] = array("PERCENT-COMPLETE", "100");
|
|
$ical_array[] = array("COMPLETED", $this->getUtcDateTime($due_date_time));
|
|
} else {
|
|
if (!empty($task->percent_complete)) {
|
|
$ical_array[] = array("PERCENT-COMPLETE", $task->percent_complete);
|
|
}
|
|
}
|
|
if ($task->priority == "Low") {
|
|
$ical_array[] = array("PRIORITY", "9");
|
|
} else {
|
|
if ($task->priority == "Medium") {
|
|
$ical_array[] = array("PRIORITY", "5");
|
|
} else {
|
|
if ($task->priority == "High") {
|
|
$ical_array[] = array("PRIORITY", "1");
|
|
}
|
|
}
|
|
}
|
|
$ical_array[] = array("END", "VTODO");
|
|
return vCal::create_ical_string_from_array($ical_array, true);
|
|
}
|
|
|
|
/**
|
|
* Creates the string for the user's events and todos between the given start
|
|
* and end times
|
|
*
|
|
* @param UserBean $user_bean the current UserBean
|
|
* @param DateTime $start_date_time the start date to search from
|
|
* @param DateTime $end_date_time the end date to search to
|
|
* @param string $dtstamp the current timestamp
|
|
* @return string the entries for events and todos
|
|
*/
|
|
protected function createSugarIcal(&$user_bean, &$start_date_time, &$end_date_time, $dtstamp)
|
|
{
|
|
$ical_array = array();
|
|
global $DO_USER_TIME_OFFSET, $sugar_config, $current_user, $timedate;
|
|
|
|
$hide_calls = false;
|
|
if (!empty($_REQUEST['hide_calls']) && $_REQUEST['hide_calls'] == "true") {
|
|
$hide_calls = true;
|
|
}
|
|
|
|
$taskAsVTODO = true;
|
|
if (!empty($_REQUEST['show_tasks_as_events']) && ($_REQUEST['show_tasks_as_events'] == "1" || $_REQUEST['show_tasks_as_events'] == "true")) {
|
|
$taskAsVTODO = false;
|
|
}
|
|
|
|
$activityList = array(
|
|
"Meetings" => array(
|
|
"showCompleted" => true,
|
|
"start" => "date_start",
|
|
"end" => "date_end"
|
|
),
|
|
"Calls" => array(
|
|
"showCompleted" => true,
|
|
"start" => "date_start",
|
|
"end" => "date_end"
|
|
),
|
|
"Tasks" => array(
|
|
"showCompleted" => true,
|
|
"start" => "date_due",
|
|
"end" => "date_due"
|
|
)
|
|
);
|
|
|
|
$acts_arr = CalendarActivity::get_activities(
|
|
$activityList,
|
|
$user_bean->id,
|
|
!$taskAsVTODO,
|
|
$start_date_time,
|
|
$end_date_time,
|
|
'month',
|
|
!$hide_calls
|
|
);
|
|
|
|
|
|
// loop thru each activity, get start/end time in UTC, and return iCal strings
|
|
foreach ($acts_arr as $act) {
|
|
$event = $act->sugar_bean;
|
|
if (!$hide_calls || ($hide_calls && $event->object_name != "Call")) {
|
|
$ical_array[] = array("BEGIN", "VEVENT");
|
|
$ical_array[] = array("SUMMARY", $event->name);
|
|
$ical_array[] = array(
|
|
"DTSTART;TZID=" . $user_bean->getPreference('timezone'),
|
|
str_replace(
|
|
"Z",
|
|
"",
|
|
(string) $timedate->tzUser($act->start_time, $current_user)->format(self::UTC_FORMAT)
|
|
)
|
|
);
|
|
$ical_array[] = array(
|
|
"DTEND;TZID=" . $user_bean->getPreference('timezone'),
|
|
str_replace(
|
|
"Z",
|
|
"",
|
|
(string) $timedate->tzUser($act->end_time, $current_user)->format(self::UTC_FORMAT)
|
|
)
|
|
);
|
|
$ical_array[] = array("DTSTAMP", $dtstamp);
|
|
$ical_array[] = array("DESCRIPTION", $event->description);
|
|
$ical_array[] = array(
|
|
"URL;VALUE=URI",
|
|
$sugar_config['site_url']."/index.php?module=".
|
|
$event->module_dir."&action=DetailView&record=". $event->id
|
|
);
|
|
$ical_array[] = array("UID", $event->id);
|
|
if ($event->object_name == "Meeting") {
|
|
$ical_array[] = array("LOCATION", $event->location);
|
|
$eventUsers = $event->get_meeting_users();
|
|
$query = "SELECT contact_id as id from meetings_contacts where meeting_id='$event->id' AND deleted=0";
|
|
$eventContacts = $event->build_related_list($query, BeanFactory::newBean('Contacts'));
|
|
$eventAttendees = array_merge($eventUsers, $eventContacts);
|
|
if (is_array($eventAttendees)) {
|
|
foreach ($eventAttendees as $attendee) {
|
|
if ($attendee->id != $user_bean->id) {
|
|
// Define the participant status
|
|
$participant_status = '';
|
|
if (!empty($attendee->accept_status)) {
|
|
switch ($attendee->accept_status) {
|
|
case 'accept':
|
|
$participant_status = ';PARTSTAT=ACCEPTED';
|
|
break;
|
|
case 'decline':
|
|
$participant_status = ';PARTSTAT=DECLINED';
|
|
break;
|
|
case 'tentative':
|
|
$participant_status = ';PARTSTAT=TENTATIVE';
|
|
break;
|
|
}
|
|
}
|
|
$ical_array[] = array(
|
|
'ATTENDEE'.$participant_status.';CN="'.$attendee->get_summary_text().'"',
|
|
'mailto:'.(!empty($attendee->email1) ? $attendee->email1:'none@none.tld')
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($event->object_name == "Call") {
|
|
$eventUsers = $event->get_call_users();
|
|
$eventContacts = $event->get_contacts();
|
|
$eventAttendees = array_merge($eventUsers, $eventContacts);
|
|
if (is_array($eventAttendees)) {
|
|
foreach ($eventAttendees as $attendee) {
|
|
if ($attendee->id != $user_bean->id) {
|
|
// Define the participant status
|
|
$participant_status = '';
|
|
if (!empty($attendee->accept_status)) {
|
|
switch ($attendee->accept_status) {
|
|
case 'accept':
|
|
$participant_status = ';PARTSTAT=ACCEPTED';
|
|
break;
|
|
case 'decline':
|
|
$participant_status = ';PARTSTAT=DECLINED';
|
|
break;
|
|
case 'tentative':
|
|
$participant_status = ';PARTSTAT=TENTATIVE';
|
|
break;
|
|
}
|
|
}
|
|
$ical_array[] = array(
|
|
'ATTENDEE'.$participant_status.';CN="'.$attendee->get_summary_text().'"',
|
|
'mailto:'.(!empty($attendee->email1) ? $attendee->email1:'none@none.tld')
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (isset($event->reminder_time) && $event->reminder_time > 0 && $event->status != "Held") {
|
|
$ical_array[] = array("BEGIN", "VALARM");
|
|
$ical_array[] = array("TRIGGER", "-PT");
|
|
$ical_array[] = array("ACTION", "DISPLAY");
|
|
$ical_array[] = array("DESCRIPTION", $event->name);
|
|
$ical_array[] = array("END", "VALARM");
|
|
}
|
|
$ical_array[] = array("END", "VEVENT");
|
|
}
|
|
}
|
|
|
|
$str = vCal::create_ical_string_from_array($ical_array, true);
|
|
|
|
require_once('include/TimeDate.php');
|
|
$timedate = new TimeDate();
|
|
$today = gmdate("Y-m-d");
|
|
$today = $timedate->handle_offset($today, $timedate->dbDayFormat, false);
|
|
|
|
require_once('modules/ProjectTask/ProjectTask.php');
|
|
$where = "project_task.assigned_user_id='{$user_bean->id}' ".
|
|
"AND (project_task.status IS NULL OR (project_task.status!='Deferred')) ".
|
|
"AND (project_task.date_start IS NULL OR " . CalendarActivity::get_occurs_within_where_clause('project_task', '', $start_date_time, $end_date_time, 'date_start', 'month') . ")";
|
|
$seedProjectTask = BeanFactory::newBean('ProjectTask');
|
|
$projectTaskList = $seedProjectTask->get_full_list("", $where);
|
|
if (is_array($projectTaskList)) {
|
|
foreach ($projectTaskList as $projectTask) {
|
|
$str .= $this->createSugarIcalTodo($user_bean, $projectTask, "ProjectTask", $dtstamp);
|
|
}
|
|
}
|
|
|
|
if ($taskAsVTODO) {
|
|
require_once('modules/Tasks/Task.php');
|
|
$where = "tasks.assigned_user_id='{$user_bean->id}' ".
|
|
"AND (tasks.status IS NULL OR (tasks.status!='Deferred')) ".
|
|
"AND (tasks.date_start IS NULL OR " . CalendarActivity::get_occurs_within_where_clause('tasks', '', $start_date_time, $end_date_time, 'date_start', 'month') . ")";
|
|
$seedTask = BeanFactory::newBean('Tasks');
|
|
$taskList = $seedTask->get_full_list("", $where);
|
|
if (is_array($taskList)) {
|
|
foreach ($taskList as $task) {
|
|
$str .= $this->createSugarIcalTodo($user_bean, $task, "Tasks", $dtstamp);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $str;
|
|
}
|
|
|
|
/**
|
|
* Gets the time zone for the given user.
|
|
*
|
|
* @param User $current_user the user
|
|
* @return DateTimeZone the user's timezone
|
|
*/
|
|
protected function getUserTimezone($current_user)
|
|
{
|
|
$gmtTZ = new DateTimeZone("UTC");
|
|
$userTZName = TimeDate::userTimezone($current_user);
|
|
if (!empty($userTZName)) {
|
|
$tz = new DateTimeZone($userTZName);
|
|
} else {
|
|
$tz = $gmtTZ;
|
|
}
|
|
return $tz;
|
|
}
|
|
|
|
/**
|
|
* Gets the daylight savings range for the given user.
|
|
*
|
|
* @param User $current_user the user
|
|
* @param integer $year the year
|
|
* @return array the start and end transitions of daylight savings
|
|
*/
|
|
protected function getDSTRange($current_user, $year)
|
|
{
|
|
$tz = $this->getUserTimezone($current_user);
|
|
$idx = 0;
|
|
$result = array();
|
|
|
|
$year_date = SugarDateTime::createFromFormat("Y", $year, new DateTimeZone("UTC"));
|
|
$year_end = clone $year_date;
|
|
$year_end->setDate((int) $year, 12, 31);
|
|
$year_end->setTime(23, 59, 59);
|
|
$year_date->setDate((int) $year, 1, 1);
|
|
$year_date->setTime(0, 0, 0);
|
|
|
|
$transitions = $tz->getTransitions($year_date->getTimestamp(), $year_end->getTimestamp());
|
|
foreach ($transitions as $transition) {
|
|
if ($transition['isdst']) {
|
|
break;
|
|
}
|
|
$idx++;
|
|
}
|
|
|
|
if (empty($transitions[$idx]["isdst"])) {
|
|
// No DST transitions found
|
|
return $result;
|
|
}
|
|
$result["start"] = $transitions[$idx]; // DST begins here
|
|
// scan till DST ends
|
|
while (isset($transitions[$idx]) && $transitions[$idx]["isdst"]) {
|
|
$idx++;
|
|
}
|
|
if (isset($transitions[$idx])) {
|
|
$result["end"] = $transitions[$idx];
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Gets the timezone string for the current user.
|
|
*
|
|
* @return string the full timezone definition including daylight savings for the iCal
|
|
*/
|
|
protected function getTimezoneString()
|
|
{
|
|
global $current_user, $timedate;
|
|
$timezoneName = $current_user->getPreference('timezone');
|
|
|
|
$gmtTZ = new DateTimeZone("UTC");
|
|
$tz = $this->getUserTimezone($current_user);
|
|
$dstRange = $this->getDSTRange($current_user, date('Y'));
|
|
|
|
$dstOffset = 0;
|
|
$gmtOffset = 0;
|
|
|
|
$ical_array = array();
|
|
$ical_array[] = array("BEGIN", "VTIMEZONE");
|
|
$ical_array[] = array("TZID", $timezoneName);
|
|
$ical_array[] = array("X-LIC-LOCATION", $timezoneName);
|
|
|
|
if (array_key_exists('start', $dstRange)) {
|
|
$dstOffset = ($dstRange['start']['offset'] / 60);
|
|
$startDate = new DateTime("@" . $dstRange["start"]["ts"], $gmtTZ);
|
|
$startstamp = strtotime($timedate->asDb($startDate));
|
|
$ical_array[] = array("BEGIN", "DAYLIGHT");
|
|
$ical_array[] = array("TZOFFSETFROM", $this->convertMinsToHoursAndMins($gmtOffset));
|
|
$ical_array[] = array("TZOFFSETTO", $this->convertMinsToHoursAndMins($dstOffset));
|
|
$ical_array[] = array("DTSTART", str_replace("Z", "", $this->getUtcTime($startstamp)));
|
|
$ical_array[] = array("END", "DAYLIGHT");
|
|
}
|
|
|
|
if (array_key_exists('end', $dstRange)) {
|
|
$gmtOffset = ($dstRange['end']['offset'] / 60);
|
|
$endDate = new DateTime("@" . $dstRange["end"]["ts"], $gmtTZ);
|
|
$endstamp = strtotime($timedate->asDb($endDate));
|
|
$ical_array[] = array("BEGIN", "STANDARD");
|
|
$ical_array[] = array("TZOFFSETFROM", $this->convertMinsToHoursAndMins($dstOffset));
|
|
$ical_array[] = array("TZOFFSETTO", $this->convertMinsToHoursAndMins($gmtOffset));
|
|
$ical_array[] = array("DTSTART", str_replace("Z", "", $this->getUtcTime($endstamp)));
|
|
$ical_array[] = array("END", "STANDARD");
|
|
}
|
|
|
|
$ical_array[] = array("END", "VTIMEZONE");
|
|
|
|
return vCal::create_ical_string_from_array($ical_array, true);
|
|
}
|
|
|
|
/**
|
|
* Generates the complete string for the calendar
|
|
*
|
|
* @param User $user_focus the user
|
|
* @param integer $num_months the number of months to search before and after today
|
|
* @return string the iCal calenar string
|
|
*/
|
|
public function getVcalIcal(&$user_focus, $num_months)
|
|
{
|
|
global $current_user, $timedate;
|
|
$current_user = $user_focus;
|
|
|
|
$cal_name = $user_focus->first_name. " ". $user_focus->last_name;
|
|
|
|
$ical_array = array();
|
|
$ical_array[] = array("BEGIN", "VCALENDAR");
|
|
$ical_array[] = array("VERSION", "2.0");
|
|
$ical_array[] = array("METHOD", "PUBLISH");
|
|
$ical_array[] = array("X-WR-CALNAME", "$cal_name (SugarCRM)");
|
|
$ical_array[] = array("PRODID", "-//SugarCRM//SugarCRM Calendar//EN");
|
|
$ical_array = array_merge($ical_array, vCal::create_ical_array_from_string($this->getTimezoneString()));
|
|
$ical_array[] = array("CALSCALE", "GREGORIAN");
|
|
|
|
$now_date_time = $timedate->getNow(true);
|
|
|
|
global $sugar_config;
|
|
$timeOffset = 2;
|
|
if (isset($sugar_config['vcal_time']) && $sugar_config['vcal_time'] != 0 && $sugar_config['vcal_time'] < 13) {
|
|
$timeOffset = $sugar_config['vcal_time'];
|
|
}
|
|
if (!empty($num_months)) {
|
|
$timeOffset = $num_months;
|
|
}
|
|
$start_date_time = $now_date_time->get("-$timeOffset months");
|
|
$end_date_time = $now_date_time->get("+$timeOffset months");
|
|
|
|
$utc_now_time = $this->getUtcDateTime($now_date_time);
|
|
|
|
$str = vCal::create_ical_string_from_array($ical_array, true);
|
|
|
|
$str .= $this->createSugarIcal($user_focus, $start_date_time, $end_date_time, $utc_now_time);
|
|
|
|
$ical_array = array(
|
|
array("DTSTAMP", $utc_now_time)
|
|
);
|
|
$ical_array[] = array("END", "VCALENDAR");
|
|
|
|
$str .= vCal::create_ical_string_from_array($ical_array, true);
|
|
|
|
return htmlspecialchars_decode(preg_replace("/&#([0-9]+)\\\\;/", '&#$1;', mb_convert_encoding($str, 'ISO-8859-1')));
|
|
}
|
|
}
|