2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
7 * This program is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU Affero General Public License version 3 as published by the
9 * Free Software Foundation with the addition of the following permission added
10 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19 * You should have received a copy of the GNU Affero General Public License along with
20 * this program; if not, see http://www.gnu.org/licenses or write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
27 * The interactive user interfaces in modified source and object code versions
28 * of this program must display Appropriate Legal Notices, as required under
29 * Section 5 of the GNU Affero General Public License version 3.
31 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32 * these Appropriate Legal Notices must retain the display of the "Powered by
33 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34 * technical reasons, the Appropriate Legal Notices must display the words
35 * "Powered by SugarCRM".
36 ********************************************************************************/
40 require_once('include/utils.php');
41 require_once('modules/Calendar/Calendar.php');
42 require_once('modules/vCals/vCal.php');
45 * Class for constructing the iCal response string for the current user.
49 class iCal extends vCal {
51 const UTC_FORMAT = 'Ymd\THi00\Z';
54 * Constructor for the iCal class.
56 public function __construct()
62 * Gets a UTC formatted string from the given dateTime
64 * @param SugarDateTime $dateTime the dateTime to format
65 * @return string the UTC formatted dateTime
67 protected function getUtcDateTime($dateTime)
69 return $dateTime->format(self::UTC_FORMAT);
73 * Gets the UTC formatted dateTime from the given timestamp.
75 * Checks the version of Sugar to see if user timezone adjustments are needed.
77 * @param integer $ts the timestamp to format
78 * @return string the UTC formatted dateTime
80 protected function getUtcTime($ts)
82 global $timedate, $sugar_version;
83 $timestamp = ($ts+(date('Z')-$timedate->adjustmentForUserTimeZone()*60));
84 return $this->getUtcDateTime(new SugarDateTime("@" . $ts));
88 * Converts the given number of minutes to formatted number of hours and remaining minutes.
90 * @param integer $minutes the number of minutes to format
91 * @return string the formatted hours and minutes
93 protected function convertMinsToHoursAndMins($minutes)
95 $hrs = floor(abs($minutes) / 60);
96 $remainderMinutes = abs($minutes) - ($hrs * 60);
97 $sign = (($minutes < 0) ? "-" : "+");
98 return $sign . str_pad($hrs, 2, "0", STR_PAD_LEFT) . str_pad($remainderMinutes, 2, "0", STR_PAD_LEFT);
102 * Create a todo entry for the given task.
104 * @param UserBean $user_bean the current UserBean
105 * @param Task $task the task for the todo entry
106 * @param string $moduleName the name of the task module
107 * @param string $dtstamp the current timestamp
108 * @return string the todo entry for the task
110 protected function createSugarIcalTodo($user_bean, $task, $moduleName, $dtstamp)
112 global $sugar_config;
113 $ical_array = array();
114 $ical_array[] = array("BEGIN", "VTODO");
115 $validDueDate = (isset($task->date_due) && $task->date_due != "" && $task->date_due != "0000-00-00");
116 $validDueTime = (isset($task->time_due) && $task->time_due != "");
123 $dateDueArr = explode("-", $task->date_due);
124 $dueYear = (int)$dateDueArr[0];
125 $dueMonth = (int)$dateDueArr[1];
126 $dueDay = (int)$dateDueArr[2];
129 $timeDueArr = explode(":", $task->time_due);
130 $dueHour = (int)$timeDueArr[0];
131 $dueMin = (int)$timeDueArr[1];
140 $due_date_time = new SugarDateTime();
141 $due_date_time->setDate($dueYear, $dueMonth, $dueDay);
142 $due_date_time->setTime($dueHour, $dueMin);
143 $ical_array[] = array(
144 "DTSTART;TZID=" . $user_bean->getPreference('timezone'),
145 str_replace("Z", "", $this->getUtcDateTime($due_date_time))
147 $ical_array[] = array("DTSTAMP", $dtstamp);
148 $ical_array[] = array("SUMMARY", $task->name);
149 $ical_array[] = array("UID", $task->id);
151 $iCalDueDate = str_replace("-", "", $task->date_due);
152 if (strlen($iCalDueDate) > 8) {
153 $iCalDueDate = substr($iCalDueDate, 0, 8);
155 $ical_array[] = array("DUE;VALUE=DATE", $iCalDueDate);
157 if ($moduleName == "ProjectTask") {
158 $ical_array[] = array(
159 "DESCRIPTION:Project",
160 $task->project_name . vCal::EOL . vCal::EOL . $task->description
163 $ical_array[] = array("DESCRIPTION", $task->description);
165 $ical_array[] = array(
167 $sugar_config['site_url']."/index.php?module=".$moduleName."&action=DetailView&record=".$task->id
169 if ($task->status == 'Completed') {
170 $ical_array[] = array("STATUS", "COMPLETED");
171 $ical_array[] = array("PERCENT-COMPLETE", "100");
172 $ical_array[] = array("COMPLETED", $this->getUtcDateTime($due_date_time));
173 } else if (!empty($task->percent_complete)) {
174 $ical_array[] = array("PERCENT-COMPLETE", $task->percent_complete);
176 if ($task->priority == "Low") {
177 $ical_array[] = array("PRIORITY", "9");
178 } else if ($task->priority == "Medium") {
179 $ical_array[] = array("PRIORITY", "5");
180 } else if ($task->priority == "High") {
181 $ical_array[] = array("PRIORITY", "1");
183 $ical_array[] = array("END", "VTODO");
184 return vCal::create_ical_string_from_array($ical_array);
188 * Creates the string for the user's events and todos between the given start
191 * @param UserBean $user_bean the current UserBean
192 * @param DateTime $start_date_time the start date to search from
193 * @param DateTime $end_date_time the end date to search to
194 * @param string $dtstamp the current timestamp
195 * @return string the entries for events and todos
197 protected function createSugarIcal(&$user_bean,&$start_date_time,&$end_date_time, $dtstamp)
199 $ical_array = array();
200 global $DO_USER_TIME_OFFSET, $sugar_config, $current_user, $timedate;
203 if (!empty($_REQUEST['hide_calls']) && $_REQUEST['hide_calls'] == "true")
209 if (!empty($_REQUEST['show_tasks_as_events']) && ($_REQUEST['show_tasks_as_events'] == "1" || $_REQUEST['show_tasks_as_events'] == "true"))
211 $taskAsVTODO = false;
214 $acts_arr = CalendarActivity::get_activities($user_bean->id,
222 // loop thru each activity, get start/end time in UTC, and return iCal strings
223 foreach($acts_arr as $act)
226 $event = $act->sugar_bean;
227 if (!$hide_calls || ($hide_calls && $event->object_name != "Call"))
229 $ical_array[] = array("BEGIN", "VEVENT");
230 $ical_array[] = array("SUMMARY", $event->name);
231 $ical_array[] = array(
232 "DTSTART;TZID=" . $user_bean->getPreference('timezone'),
236 $timedate->tzUser($act->start_time, $current_user)->format(self::UTC_FORMAT)
239 $ical_array[] = array(
240 "DTEND;TZID=" . $user_bean->getPreference('timezone'),
244 $timedate->tzUser($act->end_time, $current_user)->format(self::UTC_FORMAT)
247 $ical_array[] = array("DTSTAMP", $dtstamp);
248 $ical_array[] = array("DESCRIPTION", $event->description);
249 $ical_array[] = array(
251 $sugar_config['site_url']."/index.php?module=".
252 $event->module_dir."&action=DetailView&record=". $event->id
254 $ical_array[] = array("UID", $event->id);
255 if ($event->object_name == "Meeting")
257 $ical_array[] = array("LOCATION", $event->location);
258 $eventUsers = $event->get_meeting_users();
259 $query = "SELECT contact_id as id from meetings_contacts where meeting_id='$event->id' AND deleted=0";
260 $eventContacts = $event->build_related_list($query, new Contact());
261 $eventAttendees = array_merge($eventUsers, $eventContacts);
262 if (is_array($eventAttendees))
264 foreach($eventAttendees as $attendee)
266 if ($attendee->id != $user_bean->id)
268 // Define the participant status
269 $participant_status = '';
270 if (!empty($attendee->accept_status)) {
271 switch ($attendee->accept_status) {
273 $participant_status = ';PARTSTAT=ACCEPTED';
276 $participant_status = ';PARTSTAT=DECLINED';
279 $participant_status = ';PARTSTAT=TENTATIVE';
283 $ical_array[] = array(
284 'ATTENDEE'.$participant_status.';CN="'.$attendee->get_summary_text().'"',
285 'mailto:'.(!empty($attendee->email1) ? $attendee->email1:'none@none.tld')
291 if ($event->object_name == "Call")
293 $eventUsers = $event->get_call_users();
294 $eventContacts = $event->get_contacts();
295 $eventAttendees = array_merge($eventUsers, $eventContacts);
296 if (is_array($eventAttendees))
298 foreach($eventAttendees as $attendee)
300 if ($attendee->id != $user_bean->id)
302 // Define the participant status
303 $participant_status = '';
304 if (!empty($attendee->accept_status)) {
305 switch ($attendee->accept_status) {
307 $participant_status = ';PARTSTAT=ACCEPTED';
310 $participant_status = ';PARTSTAT=DECLINED';
313 $participant_status = ';PARTSTAT=TENTATIVE';
317 $ical_array[] = array(
318 'ATTENDEE'.$participant_status.';CN="'.$attendee->get_summary_text().'"',
319 'mailto:'.(!empty($attendee->email1) ? $attendee->email1:'none@none.tld')
325 if (isset($event->reminder_time) && $event->reminder_time > 0 && $event->status != "Held")
327 $ical_array[] = array("BEGIN", "VALARM");
328 $ical_array[] = array("TRIGGER", "-PT");
329 $ical_array[] = array("ACTION", "DISPLAY");
330 $ical_array[] = array("DESCRIPTION", $event->name);
331 $ical_array[] = array("END", "VALARM");
333 $ical_array[] = array("END", "VEVENT");
338 $str = vCal::create_ical_string_from_array($ical_array);
340 require_once('include/TimeDate.php');
341 $timedate = new TimeDate();
342 $today = gmdate("Y-m-d");
343 $today = $timedate->handle_offset($today, $timedate->dbDayFormat, false);
345 require_once('modules/ProjectTask/ProjectTask.php');
346 $where = "project_task.assigned_user_id='{$user_bean->id}' ".
347 "AND (project_task.status IS NULL OR (project_task.status!='Deferred')) ".
348 "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') . ")";
349 $seedProjectTask = new ProjectTask();
350 $projectTaskList = $seedProjectTask->get_full_list("", $where);
351 if (is_array($projectTaskList))
353 foreach($projectTaskList as $projectTask)
355 $str .= $this->createSugarIcalTodo($user_bean, $projectTask, "ProjectTask", $dtstamp);
360 require_once('modules/Tasks/Task.php');
361 $where = "tasks.assigned_user_id='{$user_bean->id}' ".
362 "AND (tasks.status IS NULL OR (tasks.status!='Deferred')) ".
363 "AND (tasks.date_start IS NULL OR " . CalendarActivity::get_occurs_within_where_clause('tasks', '', $start_date_time, $end_date_time, 'date_start', 'month') . ")";
364 $seedTask = new Task();
365 $taskList = $seedTask->get_full_list("", $where);
366 if (is_array($taskList))
368 foreach($taskList as $task)
370 $str .= $this->createSugarIcalTodo($user_bean, $task, "Tasks", $dtstamp);
379 * Gets the time zone for the given user.
381 * @param User $current_user the user
382 * @return DateTimeZone the user's timezone
384 protected function getUserTimezone($current_user)
386 $gmtTZ = new DateTimeZone("UTC");
387 $userTZName = TimeDate::userTimezone($current_user);
388 if (!empty($userTZName))
390 $tz = new DateTimeZone($userTZName);
399 * Gets the daylight savings range for the given user.
401 * @param User $current_user the user
402 * @param integer $year the year
403 * @return array the start and end transitions of daylight savings
405 protected function getDSTRange($current_user, $year)
407 $tz = $this->getUserTimezone($current_user);
411 if (version_compare(PHP_VERSION, '5.3.0') >= 0)
413 $year_date = SugarDateTime::createFromFormat("Y", $year, new DateTimeZone("UTC"));
414 $year_end = clone $year_date;
415 $year_end->setDate((int) $year, 12, 31);
416 $year_end->setTime(23, 59, 59);
417 $year_date->setDate((int) $year, 1, 1);
418 $year_date->setTime(0, 0, 0);
420 $transitions = $tz->getTransitions($year_date->getTimestamp(), $year_end->getTimestamp());
421 foreach($transitions as $transition) {
422 if($transition['isdst']) {
428 $transitions = $tz->getTransitions();
431 foreach($transitions as $transition) {
432 if($transition['isdst'] && intval(substr($transition["time"], 0, 4)) == intval(date("Y"))) {
439 if (empty($transitions[$idx]["isdst"])) {
440 // No DST transitions found
443 $result["start"] = $transitions[$idx]; // DST begins here
444 // scan till DST ends
445 while (isset($transitions[$idx]) && $transitions[$idx]["isdst"]) $idx++;
446 if(isset($transitions[$idx])) {
447 $result["end"] = $transitions[$idx];
453 * Gets the timezone string for the current user.
455 * @return string the full timezone definition including daylight savings for the iCal
457 protected function getTimezoneString()
459 global $current_user, $timedate;
460 $timezoneName = $current_user->getPreference('timezone');
462 $gmtTZ = new DateTimeZone("UTC");
463 $tz = $this->getUserTimezone($current_user);
464 $dstRange = $this->getDSTRange($current_user, date('Y'));
469 $ical_array = array();
470 $ical_array[] = array("BEGIN", "VTIMEZONE");
471 $ical_array[] = array("TZID", $timezoneName);
472 $ical_array[] = array("X-LIC-LOCATION", $timezoneName);
474 if (array_key_exists('start', $dstRange))
476 $dstOffset = ($dstRange['start']['offset'] / 60);
477 $startDate = new DateTime("@" . $dstRange["start"]["ts"], $gmtTZ);
478 $startstamp = strtotime($timedate->asDb($startDate));
479 $ical_array[] = array("BEGIN", "DAYLIGHT");
480 $ical_array[] = array("TZOFFSETFROM", $this->convertMinsToHoursAndMins($gmtOffset));
481 $ical_array[] = array("TZOFFSETTO", $this->convertMinsToHoursAndMins($dstOffset));
482 $ical_array[] = array("DTSTART", str_replace("Z", "", $this->getUtcTime($startstamp)));
483 $ical_array[] = array("END", "DAYLIGHT");
486 if (array_key_exists('end', $dstRange))
488 $gmtOffset = ($dstRange['end']['offset'] / 60);
489 $endDate = new DateTime("@" . $dstRange["end"]["ts"], $gmtTZ);
490 $endstamp = strtotime($timedate->asDb($endDate));
491 $ical_array[] = array("BEGIN", "STANDARD");
492 $ical_array[] = array("TZOFFSETFROM", $this->convertMinsToHoursAndMins($dstOffset));
493 $ical_array[] = array("TZOFFSETTO", $this->convertMinsToHoursAndMins($gmtOffset));
494 $ical_array[] = array("DTSTART", str_replace("Z", "", $this->getUtcTime($endstamp)));
495 $ical_array[] = array("END", "STANDARD");
498 $ical_array[] = array("END", "VTIMEZONE");
500 return vCal::create_ical_string_from_array($ical_array);
504 * Generates the complete string for the calendar
506 * @param User $user_focus the user
507 * @param integer $num_months the number of months to search before and after today
508 * @return string the iCal calenar string
510 function getVcalIcal(&$user_focus, $num_months)
512 global $current_user, $timedate;
513 $current_user = $user_focus;
515 $cal_name = $user_focus->first_name. " ". $user_focus->last_name;
517 $ical_array = array();
518 $ical_array[] = array("BEGIN", "VCALENDAR");
519 $ical_array[] = array("VERSION", "2.0");
520 $ical_array[] = array("METHOD", "PUBLISH");
521 $ical_array[] = array("X-WR-CALNAME", "$cal_name (SugarCRM)");
522 $ical_array[] = array("PRODID", "-//SugarCRM//SugarCRM Calendar//EN");
523 $timezonestr = explode(":", $this->getTimezoneString(), 2);
524 $ical_array[] = array($timezonestr[0], $timezonestr[1]);
525 $ical_array[] = array("CALSCALE", "GREGORIAN");
527 $now_date_time = $timedate->getNow(true);
529 global $sugar_config;
531 if (isset($sugar_config['vcal_time']) && $sugar_config['vcal_time'] != 0 && $sugar_config['vcal_time'] < 13)
533 $timeOffset = $sugar_config['vcal_time'];
535 if (!empty($num_months))
537 $timeOffset = $num_months;
539 $start_date_time = $now_date_time->get("-$timeOffset months");
540 $end_date_time = $now_date_time->get("+$timeOffset months");
542 $utc_now_time = $this->getUtcDateTime($now_date_time);
544 $str = vCal::create_ical_string_from_array($ical_array);
546 $str .= $this->createSugarIcal($user_focus,$start_date_time,$end_date_time,$utc_now_time);
549 array("DTSTAMP", $utc_now_time)
551 $ical_array[] = array("END", "VCALENDAR");
553 $str .= vCal::create_ical_string_from_array($ical_array);