]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/iCals/iCal.php
Release 6.5.0
[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-2012 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     * Escapes new lines in the given string,
63     *
64     * @param string $string the original string
65     * @return string the string with new lines properly escaped
66     */
67     protected function escapeNls($string)
68     {
69         $str = str_replace("\r\n", "\\n", $string);
70         return $str;
71     }
72
73     /**
74     * Gets a UTC formatted string from the given dateTime
75     *
76     * @param SugarDateTime $dateTime the dateTime to format
77     * @return string the UTC formatted dateTime
78     */
79     protected function getUtcDateTime($dateTime)
80     {
81         return $dateTime->format(self::UTC_FORMAT);
82     }
83
84     /**
85     * Gets the UTC formatted dateTime from the given timestamp.
86     *
87     * Checks the version of Sugar to see if user timezone adjustments are needed.
88     *
89     * @param integer $ts the timestamp to format
90     * @return string the UTC formatted dateTime
91     */
92     protected function getUtcTime($ts)
93     {
94         global $timedate, $sugar_version;
95         if (substr($sugar_version, 0, 1) > 5) {
96             $timestamp = ($ts+(date('Z')-$timedate->adjustmentForUserTimeZone()*60));
97         } else {
98             $timestamp = ($ts);
99         }
100         return $this->getUtcDateTime(new SugarDateTime("@" . $ts));
101     }
102
103     /**
104     * Converts the given number of minutes to formatted number of hours and remaining minutes.
105     *
106     * @param integer $minutes the number of minutes to format
107     * @return string the formatted hours and minutes
108     */
109     protected function convertMinsToHoursAndMins($minutes)
110     {
111         $hrs = floor(abs($minutes) / 60);
112         $remainderMinutes = abs($minutes) - ($hrs * 60);
113         $sign = (($minutes < 0) ? "-" : "+");
114         return $sign . str_pad($hrs, 2, "0", STR_PAD_LEFT) . str_pad($remainderMinutes, 2, "0", STR_PAD_LEFT);
115     }
116
117     /**
118     * Create a todo entry for the given task.
119     *
120     * @param UserBean $user_bean the current UserBean
121     * @param Task $task the task for the todo entry
122     * @param string $moduleName the name of the task module
123     * @param string $dtstamp the current timestamp
124     * @return string the todo entry for the task
125     */
126     protected function createSugarIcalTodo($user_bean, $task, $moduleName, $dtstamp)
127     {
128         global $sugar_config;
129         $str = "";
130         $str .= "BEGIN:VTODO\n";
131         $validDueDate = (isset($task->date_due) && $task->date_due != "" && $task->date_due != "0000-00-00");
132         $validDueTime = (isset($task->time_due) && $task->time_due != "");
133         $dueYear = 1970;
134         $dueMonth = 1;
135         $dueDay = 1;
136         $dueHour = 0;
137         $dueMin = 0;
138         if ($validDueDate) {
139             $dateDueArr = split("-", $task->date_due);
140             $dueYear = (int)$dateDueArr[0];
141             $dueMonth = (int)$dateDueArr[1];
142             $dueDay = (int)$dateDueArr[2];
143
144             if ($validDueTime) {
145                 $timeDueArr = split(":", $task->time_due);
146                 $dueHour = (int)$timeDueArr[0];
147                 $dueMin = (int)$timeDueArr[1];
148             }
149         }
150         $date_arr = array(
151              'day'=>$dueDay,
152              'month'=>$dueMonth,
153              'hour'=>$dueHour,
154              'min'=>$dueMin,
155              'year'=>$dueYear);
156         $due_date_time = new SugarDateTime();
157         $due_date_time->setDate($dueYear, $dueMonth, $dueDay);
158         $due_date_time->setTime($dueHour, $dueMin);
159         $str .= "DTSTART;TZID=" . $user_bean->getPreference('timezone') . ":" .
160                     str_replace("Z", "", $this->getUtcDateTime($due_date_time)) . "\n";
161         $str .= "DTSTAMP:" . $dtstamp . "\n";
162         $str .= "SUMMARY:" . $task->name . "\n";
163         $str .= "UID:" . $task->id . "\n";
164         if ($validDueDate) {
165             $iCalDueDate = str_replace("-", "", $task->date_due);
166             if (strlen($iCalDueDate) > 8) {
167                 $iCalDueDate = substr($iCalDueDate, 0, 8);
168             }
169             $str .= "DUE;VALUE=DATE:" . $iCalDueDate . "\n";
170         }
171         if ($moduleName == "ProjectTask") {
172             $str .= "DESCRIPTION:Project: " . $task->project_name. "\\n\\n" .
173                 $this->escapeNls($task->description). "\n";
174         } else {
175             $str .= "DESCRIPTION:" . $this->escapeNls($task->description). "\n";
176         }
177         $str .= "URL;VALUE=URI:" . $sugar_config['site_url'].
178             "/index.php?module=".$moduleName."&action=DetailView&record=". $task->id. "\n";
179         if ($task->status == 'Completed') {
180             $str .= "STATUS:COMPLETED\n";
181             $str .= "PERCENT-COMPLETE:100\n";
182             $str .= "COMPLETED:" . $this->getUtcDateTime($due_date_time) . "\n";
183         } else if (!empty($task->percent_complete)) {
184             $str .= "PERCENT-COMPLETE:" . $task->percent_complete . "\n";
185         }
186         if ($task->priority == "Low") {
187             $str .= "PRIORITY:9\n";
188         } else if ($task->priority == "Medium") {
189                 $str .= "PRIORITY:5\n";
190         } else if ($task->priority == "High") {
191                 $str .= "PRIORITY:1\n";
192         }
193         $str .= "END:VTODO\n";
194         return $str;
195     }
196
197     /**
198     * Creates the string for the user's events and todos between the given start
199     * and end times
200     *
201     * @param UserBean $user_bean the current UserBean
202     * @param DateTime $start_date_time the start date to search from
203     * @param DateTime $end_date_time the end date to search to
204     * @param string $dtstamp the current timestamp
205     * @return string the entries for events and todos
206     */
207     protected function createSugarIcal(&$user_bean,&$start_date_time,&$end_date_time, $dtstamp)
208     {
209         $str = '';
210         global $DO_USER_TIME_OFFSET, $sugar_config, $current_user, $timedate;
211
212         $acts_arr = CalendarActivity::get_activities($user_bean->id,
213             false,
214             $start_date_time,
215             $end_date_time,
216             'month');
217
218         $hide_calls = false;
219         if (!empty($_REQUEST['hide_calls']) && $_REQUEST['hide_calls'] == "true")
220         {
221             $hide_calls = true;
222         }
223
224         // loop thru each activity, get start/end time in UTC, and return iCal strings
225         foreach($acts_arr as $act)
226         {
227
228             $event = $act->sugar_bean;
229             if (!$hide_calls || ($hide_calls && $event->object_name != "Call"))
230             {
231                 $str .= "BEGIN:VEVENT\n";
232                 $str .= "SUMMARY:" . $event->name . "\n";
233                 $str .= "DTSTART;TZID=" . $user_bean->getPreference('timezone') . ":" .
234                         str_replace("Z", "", $timedate->tzUser($act->start_time, $current_user)->format(self::UTC_FORMAT)) . "\n";
235                 $str .= "DTEND;TZID=" . $user_bean->getPreference('timezone') . ":" .
236                         str_replace("Z", "", $timedate->tzUser($act->end_time, $current_user)->format(self::UTC_FORMAT)) . "\n";
237                 $str .= "DTSTAMP:" . $dtstamp . "\n";
238                 $str .= "DESCRIPTION:" . $this->escapeNls($event->description) . "\n";
239                 $str .= "URL;VALUE=URI:" . $sugar_config['site_url'].
240                     "/index.php?module=".$event->module_dir."&action=DetailView&record=". $event->id. "\n";
241                 $str .= "UID:" . $event->id . "\n";
242                 if ($event->object_name == "Meeting")
243                 {
244                     $str .= "LOCATION:" . $event->location . "\n";
245                     $eventUsers = $event->get_meeting_users();
246                     $query = "SELECT contact_id as id from meetings_contacts where meeting_id='$event->id' AND deleted=0";
247                     $eventContacts = $event->build_related_list($query, new Contact());
248                     $eventAttendees = array_merge($eventUsers, $eventContacts);
249                     if (is_array($eventAttendees))
250                     {
251                         foreach($eventAttendees as $attendee)
252                         {
253                             if ($attendee->id != $user_bean->id)
254                             {
255                                 $str .= 'ATTENDEE;CN="'.$attendee->get_summary_text().'":mailto:'. $attendee->email1 . "\n";
256                             }
257                         }
258                     }
259                 }
260                 if ($event->object_name == "Call")
261                 {
262                     $eventUsers = $event->get_call_users();
263                     $eventContacts = $event->get_contacts();
264                     $eventAttendees = array_merge($eventUsers, $eventContacts);
265                     if (is_array($eventAttendees))
266                     {
267                         foreach($eventAttendees as $attendee)
268                         {
269                             if ($attendee->id != $user_bean->id)
270                             {
271                                 $str .= 'ATTENDEE;CN="'.$attendee->get_summary_text().'":mailto:'. $attendee->email1 . "\n";
272                             }
273                         }
274                     }
275                 }
276                 if ($event->reminder_time > 0 && $event->status != "Held")
277                 {
278                     $str .= "BEGIN:VALARM\n";
279                     $str .= "TRIGGER:-PT" . $event->reminder_time/60 . "M\n";
280                     $str .= "ACTION:DISPLAY\n";
281                     $str .= "DESCRIPTION:" . $event->name . "\n";
282                     $str .= "END:VALARM\n";
283                 }
284                 $str .= "END:VEVENT\n";
285             }
286
287         }
288
289         require_once('include/TimeDate.php');
290         $timedate = new TimeDate();
291         $today = gmdate("Y-m-d");
292         $today = $timedate->handle_offset($today, $timedate->dbDayFormat, false);
293
294         require_once('modules/ProjectTask/ProjectTask.php');
295         $where = "project_task.assigned_user_id='{$user_bean->id}' ".
296             "AND (project_task.status IS NULL OR (project_task.status!='Deferred')) ".
297             "AND (project_task.date_start IS NULL OR project_task.date_start <= '$today')";
298         $seedProjectTask = new ProjectTask();
299         $projectTaskList = $seedProjectTask->get_full_list("", $where);
300         if (is_array($projectTaskList))
301         {
302             foreach($projectTaskList as $projectTask)
303             {
304                 $str .= $this->createSugarIcalTodo($user_bean, $projectTask, "ProjectTask", $dtstamp);
305             }
306         }
307
308         require_once('modules/Tasks/Task.php');
309         $where = "tasks.assigned_user_id='{$user_bean->id}' ".
310             "AND (tasks.status IS NULL OR (tasks.status!='Deferred')) ".
311             "AND (tasks.date_start IS NULL OR tasks.date_start <= '$today')";
312         $seedTask = new Task();
313         $taskList = $seedTask->get_full_list("", $where);
314         if (is_array($taskList))
315         {
316             foreach($taskList as $task)
317             {
318                 $str .= $this->createSugarIcalTodo($user_bean, $task, "Tasks", $dtstamp);
319             }
320         }
321
322         return $str;
323     }
324
325     /**
326     * Gets the time zone for the given user.
327     *
328     * @param User $current_user the user
329     * @return DateTimeZone the user's timezone
330     */
331     protected function getUserTimezone($current_user)
332     {
333         $gmtTZ = new DateTimeZone("UTC");
334         $userTZName = TimeDate::userTimezone($current_user);
335         if (!empty($userTZName))
336         {
337             $tz = new DateTimeZone($userTZName);
338         } else
339         {
340             $tz = $gmtTZ;
341         }
342         return $tz;
343     }
344
345     /**
346     * Gets the daylight savings range for the given user.
347     *
348     * @param User $current_user the user
349     * @param integer $year the year
350     * @return array the start and end transitions of daylight savings
351     */
352     protected function getDSTRange($current_user, $year)
353     {
354         $tz = $this->getUserTimezone($current_user);
355         $idx = 0;
356         $result = array();
357
358         if (version_compare(PHP_VERSION, '5.3.0') >= 0)
359         {
360             $year_date = SugarDateTime::createFromFormat("Y", $year, new DateTimeZone("UTC"));
361             $year_end = clone $year_date;
362             $year_end->setDate((int) $year, 12, 31);
363             $year_end->setTime(23, 59, 59);
364             $year_date->setDate((int) $year, 1, 1);
365             $year_date->setTime(0, 0, 0);
366
367             $transitions = $tz->getTransitions($year_date->getTimestamp(), $year_end->getTimestamp());
368             foreach($transitions as $transition) {
369                 if($transition['isdst']) {
370                     break;
371                 }
372                 $idx++;
373             }
374         } else {
375             $transitions = $tz->getTransitions();
376
377             $idx = 0;
378             foreach($transitions as $transition) {
379                 if($transition['isdst'] && intval(substr($transition["time"], 0, 4)) == intval(date("Y"))) {
380                     break;
381                 }
382                 $idx++;
383             }
384         }
385
386         if (empty($transitions[$idx]["isdst"])) {
387             // No DST transitions found
388             return $result;
389         }
390         $result["start"] = $transitions[$idx]; // DST begins here
391         // scan till DST ends
392         while (isset($transitions[$idx]) && $transitions[$idx]["isdst"]) $idx++;
393         if(isset($transitions[$idx])) {
394             $result["end"] = $transitions[$idx];
395         }
396         return $result;
397     }
398
399     /**
400     * Gets the timezone string for the current user.
401     *
402     * @return string the full timezone definition including daylight savings for the iCal
403     */
404     protected function getTimezoneString()
405     {
406         global $current_user, $timedate;
407         $timezoneName = $current_user->getPreference('timezone');
408
409         $gmtTZ = new DateTimeZone("UTC");
410         $tz = $this->getUserTimezone($current_user);
411         $dstRange = $this->getDSTRange($current_user, date('Y'));
412
413         $dstOffset = 0;
414         $gmtOffset = 0;
415
416         $timezoneString = "BEGIN:VTIMEZONE\n";
417         $timezoneString .= "TZID:" . $timezoneName . "\n";
418         $timezoneString .= "X-LIC-LOCATION:" . $timezoneName . "\n";
419
420         if (array_key_exists('start', $dstRange))
421         {
422             $dstOffset = ($dstRange['start']['offset'] / 60);
423             $startDate = new DateTime("@" . $dstRange["start"]["ts"], $gmtTZ);
424             $startstamp = strtotime($timedate->asDb($startDate));
425             $timezoneString .= "BEGIN:DAYLIGHT\n";
426             $timezoneString .= "TZOFFSETFROM:" . $this->convertMinsToHoursAndMins($gmtOffset) . "\n";
427             $timezoneString .= "TZOFFSETTO:" . $this->convertMinsToHoursAndMins($dstOffset) . "\n";
428             $timezoneString .= "DTSTART:" . str_replace("Z", "", $this->getUtcTime($startstamp)) . "\n";
429             $timezoneString .= "END:DAYLIGHT\n";
430         }
431
432         if (array_key_exists('end', $dstRange))
433         {
434             $gmtOffset = ($dstRange['end']['offset'] / 60);
435             $endDate = new DateTime("@" . $dstRange["end"]["ts"], $gmtTZ);
436             $endstamp = strtotime($timedate->asDb($endDate));
437             $timezoneString .= "BEGIN:STANDARD\n";
438             $timezoneString .= "TZOFFSETFROM:" . $this->convertMinsToHoursAndMins($dstOffset) . "\n";
439             $timezoneString .= "TZOFFSETTO:" . $this->convertMinsToHoursAndMins($gmtOffset) . "\n";
440             $timezoneString .= "DTSTART:" . str_replace("Z", "", $this->getUtcTime($endstamp)) . "\n";
441             $timezoneString .= "END:STANDARD\n";
442         }
443
444         $timezoneString .= "END:VTIMEZONE\n";
445
446         return $timezoneString;
447     }
448
449     /**
450     * Generates the complete string for the calendar
451     *
452     * @param User $user_focus the user
453     * @param integer $num_months the number of months to search before and after today
454     * @return string the iCal calenar string
455     */
456     function getVcalIcal(&$user_focus, $num_months)
457     {
458         global $current_user, $timedate;
459         $current_user = $user_focus;
460
461         $cal_name = $user_focus->first_name. " ". $user_focus->last_name;
462
463         $str = "BEGIN:VCALENDAR\n";
464         $str .= "VERSION:2.0\n";
465         $str .= "METHOD:PUBLISH\n";
466         $str .= "X-WR-CALNAME:$cal_name (SugarCRM)\n";
467         $str .= "PRODID:-//SugarCRM//SugarCRM Calendar//EN\n";
468         $str .= $this->getTimezoneString();
469         $str .= "CALSCALE:GREGORIAN\n";
470
471         $now_date_time = $timedate->getNow(true);
472
473         global $sugar_config;
474         $timeOffset = 2;
475         if (isset($sugar_config['vcal_time']) && $sugar_config['vcal_time'] != 0 && $sugar_config['vcal_time'] < 13)
476         {
477             $timeOffset = $sugar_config['vcal_time'];
478         }
479         if (!empty($num_months))
480         {
481             $timeOffset = $num_months;
482         }
483         $start_date_time = $now_date_time->get("-$timeOffset months");
484         $end_date_time = $now_date_time->get("+$timeOffset months");
485
486         $utc_now_time = $this->getUtcDateTime($now_date_time);
487
488         $str .= $this->createSugarIcal($user_focus,$start_date_time,$end_date_time,$utc_now_time);
489
490         $str .= "DTSTAMP:" . $utc_now_time . "\n";
491         $str .= "END:VCALENDAR\n";
492
493         return $str;
494     }
495
496 }
497
498 ?>