]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/iCals/iCal.php
Release 6.5.16
[Github/sugarcrm.git] / modules / iCals / iCal.php
1 <?php
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.
6  * 
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.
13  * 
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
17  * details.
18  * 
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
22  * 02110-1301 USA.
23  * 
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.
26  * 
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.
30  * 
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  ********************************************************************************/
37
38
39
40 require_once('include/utils.php');
41 require_once('modules/Calendar/Calendar.php');
42 require_once('modules/vCals/vCal.php');
43
44 /**
45 * Class for constructing the iCal response string for the current user.
46 *
47 * @see vCal
48 */
49 class iCal extends vCal {
50
51     const UTC_FORMAT = 'Ymd\THi00\Z';
52
53     /**
54     * Constructor for the iCal class.
55     */
56     public function __construct()
57     {
58         parent::vCal();
59     }
60
61     /**
62     * Gets a UTC formatted string from the given dateTime
63     *
64     * @param SugarDateTime $dateTime the dateTime to format
65     * @return string the UTC formatted dateTime
66     */
67     protected function getUtcDateTime($dateTime)
68     {
69         return $dateTime->format(self::UTC_FORMAT);
70     }
71
72     /**
73     * Gets the UTC formatted dateTime from the given timestamp.
74     *
75     * Checks the version of Sugar to see if user timezone adjustments are needed.
76     *
77     * @param integer $ts the timestamp to format
78     * @return string the UTC formatted dateTime
79     */
80     protected function getUtcTime($ts)
81     {
82         global $timedate, $sugar_version;
83         $timestamp = ($ts+(date('Z')-$timedate->adjustmentForUserTimeZone()*60));
84         return $this->getUtcDateTime(new SugarDateTime("@" . $ts));
85     }
86
87     /**
88     * Converts the given number of minutes to formatted number of hours and remaining minutes.
89     *
90     * @param integer $minutes the number of minutes to format
91     * @return string the formatted hours and minutes
92     */
93     protected function convertMinsToHoursAndMins($minutes)
94     {
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);
99     }
100
101     /**
102     * Create a todo entry for the given task.
103     *
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
109     */
110     protected function createSugarIcalTodo($user_bean, $task, $moduleName, $dtstamp)
111     {
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 != "");
117         $dueYear = 1970;
118         $dueMonth = 1;
119         $dueDay = 1;
120         $dueHour = 0;
121         $dueMin = 0;
122         if ($validDueDate) {
123             $dateDueArr = explode("-", $task->date_due);
124             $dueYear = (int)$dateDueArr[0];
125             $dueMonth = (int)$dateDueArr[1];
126             $dueDay = (int)$dateDueArr[2];
127
128             if ($validDueTime) {
129                 $timeDueArr = explode(":", $task->time_due);
130                 $dueHour = (int)$timeDueArr[0];
131                 $dueMin = (int)$timeDueArr[1];
132             }
133         }
134         $date_arr = array(
135              'day'=>$dueDay,
136              'month'=>$dueMonth,
137              'hour'=>$dueHour,
138              'min'=>$dueMin,
139              'year'=>$dueYear);
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))
146         );
147         $ical_array[] = array("DTSTAMP", $dtstamp);
148         $ical_array[] = array("SUMMARY", $task->name);
149         $ical_array[] = array("UID", $task->id);
150         if ($validDueDate) {
151             $iCalDueDate = str_replace("-", "", $task->date_due);
152             if (strlen($iCalDueDate) > 8) {
153                 $iCalDueDate = substr($iCalDueDate, 0, 8);
154             }
155             $ical_array[] = array("DUE;VALUE=DATE", $iCalDueDate);
156         }
157         if ($moduleName == "ProjectTask") {
158             $ical_array[] = array(
159                 "DESCRIPTION:Project",
160                 $task->project_name . vCal::EOL . vCal::EOL . $task->description
161             );
162         } else {
163             $ical_array[] = array("DESCRIPTION", $task->description);
164         }
165         $ical_array[] = array(
166             "URL;VALUE=URI",
167             $sugar_config['site_url']."/index.php?module=".$moduleName."&action=DetailView&record=".$task->id
168         );
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);
175         }
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");
182         }
183         $ical_array[] = array("END", "VTODO");
184         return vCal::create_ical_string_from_array($ical_array);
185     }
186
187     /**
188     * Creates the string for the user's events and todos between the given start
189     * and end times
190     *
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
196     */
197     protected function createSugarIcal(&$user_bean,&$start_date_time,&$end_date_time, $dtstamp)
198     {
199         $ical_array = array();
200         global $DO_USER_TIME_OFFSET, $sugar_config, $current_user, $timedate;
201
202         $hide_calls = false;
203         if (!empty($_REQUEST['hide_calls']) && $_REQUEST['hide_calls'] == "true")
204         {
205             $hide_calls = true;
206         }
207
208         $taskAsVTODO = true;
209         if (!empty($_REQUEST['show_tasks_as_events']) && ($_REQUEST['show_tasks_as_events'] == "1"  || $_REQUEST['show_tasks_as_events'] == "true"))
210         {
211             $taskAsVTODO = false;
212         }
213
214         $acts_arr = CalendarActivity::get_activities($user_bean->id,
215             !$taskAsVTODO,
216             $start_date_time,
217             $end_date_time,
218             'month',
219             !$hide_calls);
220
221
222         // loop thru each activity, get start/end time in UTC, and return iCal strings
223         foreach($acts_arr as $act)
224         {
225
226             $event = $act->sugar_bean;
227             if (!$hide_calls || ($hide_calls && $event->object_name != "Call"))
228             {
229                 $ical_array[] = array("BEGIN", "VEVENT");
230                 $ical_array[] = array("SUMMARY", $event->name);
231                 $ical_array[] = array(
232                     "DTSTART;TZID=" . $user_bean->getPreference('timezone'),
233                     str_replace(
234                         "Z",
235                         "",
236                         $timedate->tzUser($act->start_time, $current_user)->format(self::UTC_FORMAT)
237                     )
238                 );
239                 $ical_array[] = array(
240                     "DTEND;TZID=" . $user_bean->getPreference('timezone'),
241                     str_replace(
242                         "Z",
243                         "",
244                         $timedate->tzUser($act->end_time, $current_user)->format(self::UTC_FORMAT)
245                     )
246                 );
247                 $ical_array[] = array("DTSTAMP", $dtstamp);
248                 $ical_array[] = array("DESCRIPTION", $event->description);
249                 $ical_array[] = array(
250                     "URL;VALUE=URI",
251                     $sugar_config['site_url']."/index.php?module=".
252                     $event->module_dir."&action=DetailView&record=". $event->id
253                 );
254                 $ical_array[] = array("UID", $event->id);
255                 if ($event->object_name == "Meeting")
256                 {
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))
263                     {
264                         foreach($eventAttendees as $attendee)
265                         {
266                             if ($attendee->id != $user_bean->id)
267                             {
268                                 // Define the participant status
269                                 $participant_status = '';
270                                 if (!empty($attendee->accept_status)) {
271                                     switch ($attendee->accept_status) {
272                                         case 'accept':
273                                             $participant_status = ';PARTSTAT=ACCEPTED';
274                                             break;
275                                         case 'decline':
276                                             $participant_status = ';PARTSTAT=DECLINED';
277                                             break;
278                                         case 'tentative':
279                                             $participant_status = ';PARTSTAT=TENTATIVE';
280                                             break;
281                                     }
282                                 }
283                                 $ical_array[] = array(
284                                     'ATTENDEE'.$participant_status.';CN="'.$attendee->get_summary_text().'"',
285                                     'mailto:'.(!empty($attendee->email1) ? $attendee->email1:'none@none.tld')
286                                 );
287                             }
288                         }
289                     }
290                 }
291                 if ($event->object_name == "Call")
292                 {
293                     $eventUsers = $event->get_call_users();
294                     $eventContacts = $event->get_contacts();
295                     $eventAttendees = array_merge($eventUsers, $eventContacts);
296                     if (is_array($eventAttendees))
297                     {
298                         foreach($eventAttendees as $attendee)
299                         {
300                             if ($attendee->id != $user_bean->id)
301                             {
302                                 // Define the participant status
303                                 $participant_status = '';
304                                 if (!empty($attendee->accept_status)) {
305                                     switch ($attendee->accept_status) {
306                                         case 'accept':
307                                             $participant_status = ';PARTSTAT=ACCEPTED';
308                                             break;
309                                         case 'decline':
310                                             $participant_status = ';PARTSTAT=DECLINED';
311                                             break;
312                                         case 'tentative':
313                                             $participant_status = ';PARTSTAT=TENTATIVE';
314                                             break;
315                                     }
316                                 }
317                                 $ical_array[] = array(
318                                     'ATTENDEE'.$participant_status.';CN="'.$attendee->get_summary_text().'"',
319                                     'mailto:'.(!empty($attendee->email1) ? $attendee->email1:'none@none.tld')
320                                 );
321                             }
322                         }
323                     }
324                 }
325                 if (isset($event->reminder_time) && $event->reminder_time > 0 && $event->status != "Held")
326                 {
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");
332                 }
333                 $ical_array[] = array("END", "VEVENT");
334             }
335
336         }
337
338         $str = vCal::create_ical_string_from_array($ical_array);
339
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);
344
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))
352         {
353             foreach($projectTaskList as $projectTask)
354             {
355                 $str .= $this->createSugarIcalTodo($user_bean, $projectTask, "ProjectTask", $dtstamp);
356             }
357         }
358
359         if ($taskAsVTODO) {
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))
367             {
368                 foreach($taskList as $task)
369                 {
370                     $str .= $this->createSugarIcalTodo($user_bean, $task, "Tasks", $dtstamp);
371                 }
372             }
373         }
374         
375         return $str;
376     }
377
378     /**
379     * Gets the time zone for the given user.
380     *
381     * @param User $current_user the user
382     * @return DateTimeZone the user's timezone
383     */
384     protected function getUserTimezone($current_user)
385     {
386         $gmtTZ = new DateTimeZone("UTC");
387         $userTZName = TimeDate::userTimezone($current_user);
388         if (!empty($userTZName))
389         {
390             $tz = new DateTimeZone($userTZName);
391         } else
392         {
393             $tz = $gmtTZ;
394         }
395         return $tz;
396     }
397
398     /**
399     * Gets the daylight savings range for the given user.
400     *
401     * @param User $current_user the user
402     * @param integer $year the year
403     * @return array the start and end transitions of daylight savings
404     */
405     protected function getDSTRange($current_user, $year)
406     {
407         $tz = $this->getUserTimezone($current_user);
408         $idx = 0;
409         $result = array();
410
411         if (version_compare(PHP_VERSION, '5.3.0') >= 0)
412         {
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);
419
420             $transitions = $tz->getTransitions($year_date->getTimestamp(), $year_end->getTimestamp());
421             foreach($transitions as $transition) {
422                 if($transition['isdst']) {
423                     break;
424                 }
425                 $idx++;
426             }
427         } else {
428             $transitions = $tz->getTransitions();
429
430             $idx = 0;
431             foreach($transitions as $transition) {
432                 if($transition['isdst'] && intval(substr($transition["time"], 0, 4)) == intval(date("Y"))) {
433                     break;
434                 }
435                 $idx++;
436             }
437         }
438
439         if (empty($transitions[$idx]["isdst"])) {
440             // No DST transitions found
441             return $result;
442         }
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];
448         }
449         return $result;
450     }
451
452     /**
453     * Gets the timezone string for the current user.
454     *
455     * @return string the full timezone definition including daylight savings for the iCal
456     */
457     protected function getTimezoneString()
458     {
459         global $current_user, $timedate;
460         $timezoneName = $current_user->getPreference('timezone');
461
462         $gmtTZ = new DateTimeZone("UTC");
463         $tz = $this->getUserTimezone($current_user);
464         $dstRange = $this->getDSTRange($current_user, date('Y'));
465
466         $dstOffset = 0;
467         $gmtOffset = 0;
468
469         $ical_array = array();
470         $ical_array[] = array("BEGIN", "VTIMEZONE");
471         $ical_array[] = array("TZID", $timezoneName);
472         $ical_array[] = array("X-LIC-LOCATION", $timezoneName);
473
474         if (array_key_exists('start', $dstRange))
475         {
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");
484         }
485
486         if (array_key_exists('end', $dstRange))
487         {
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");
496         }
497
498         $ical_array[] = array("END", "VTIMEZONE");
499
500         return vCal::create_ical_string_from_array($ical_array);
501     }
502
503     /**
504     * Generates the complete string for the calendar
505     *
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
509     */
510     function getVcalIcal(&$user_focus, $num_months)
511     {
512         global $current_user, $timedate;
513         $current_user = $user_focus;
514
515         $cal_name = $user_focus->first_name. " ". $user_focus->last_name;
516
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");
526
527         $now_date_time = $timedate->getNow(true);
528
529         global $sugar_config;
530         $timeOffset = 2;
531         if (isset($sugar_config['vcal_time']) && $sugar_config['vcal_time'] != 0 && $sugar_config['vcal_time'] < 13)
532         {
533             $timeOffset = $sugar_config['vcal_time'];
534         }
535         if (!empty($num_months))
536         {
537             $timeOffset = $num_months;
538         }
539         $start_date_time = $now_date_time->get("-$timeOffset months");
540         $end_date_time = $now_date_time->get("+$timeOffset months");
541
542         $utc_now_time = $this->getUtcDateTime($now_date_time);
543
544         $str = vCal::create_ical_string_from_array($ical_array);
545
546         $str .= $this->createSugarIcal($user_focus,$start_date_time,$end_date_time,$utc_now_time);
547
548         $ical_array = array(
549             array("DTSTAMP", $utc_now_time)
550         );
551         $ical_array[] = array("END", "VCALENDAR");
552
553         $str .= vCal::create_ical_string_from_array($ical_array);
554
555         return $str;
556     }
557
558 }