/
var
/
www
/
html
/
sugardemo
/
modules
/
SNIP
/
Upload File
HOME
<?php /* * Your installation or use of this SugarCRM file is subject to the applicable * terms available at * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/. * If you do not agree to all of the applicable terms or do not have the * authority to bind the entity as an authorized representative, then do not * install or use this SugarCRM file. * * Copyright (C) SugarCRM Inc. All rights reserved. */ /** * Stub class for the future of SugarEvents */ class Event { public $date_start; public $date_end; public $name; public $status; public $description; public $type = 'Event'; public $date_created; public $date_modified; public $url; public $uid; public $sequence; public $location; public $attendees = []; public $organizer; } /** * Base class for all vObjects */ class vBasic { /** * @var \Event */ public $event; protected $properties = []; protected $stack = []; protected $parent = null; public function __construct() { $this->event = new Event(); } /** * Takes in a vCalendar formatted date and generates a (Y-m-d H:i:s) formatted date string * @param object $key - this is parsed to determine any timezone information * @param object $date - the date in vCal format (Ymd\THis\Z) * @return date formated string (Y-m-d H:i:s) */ public function parseDate($key, $date) { static $gmt = null; if (!$gmt) { $gmt = new DateTimeZone('GMT'); } $keys = explode(';', $key); $timezone = null; foreach ($keys as $k) { if (substr_count($k, 'TZID=')) { $tz = explode('=', $k); // trim spaces and quotes since some clients quote timezones $timezone = @timezone_open(trim($tz[1], '" ')); if (!$timezone) { $timezone = vTimeZone::guessTimezone($tz[1]); } } } if ($timezone) { $dt = new DateTime($date, $timezone); $dt->setTimezone($gmt); } else { $dt = new DateTime($date); } return $dt->format('Y-m-d H:i:s'); } /** * Takes in a Sugar Formatted date (Y-m-d H:i:s) and changes it to a VCal Formatted one (Ymd\THis\Z) * @param STRING $date - date in (Y-m-d H:i:s) * @return STRING date - vCal Formatted */ public function toVCalDate($date) { return date('Ymd\THis\Z', strtotime($date)); } /** * returns the stack of events/todos/journals/FreeBusy/alarms/timezones for the given object * @return ARRAY of events */ public function getStack() { return $this->stack(); } /** * returns the properties for the given object * @return ARRAY of properties */ public function getProperties() { return $this->properties(); } /** * default process action is to just set the properties * @param object $key * @param object $value * */ public function process($key, $value) { $this->properties[$key] = $value; } /** * returns the VCal String for the given object * @return STRING */ public function generateVCal() { return ''; } } /** * */ class vEvent extends vBasic { /** * Generates the VCAL for the event * @return */ public function generateVCal() { $cal = <<<VEVENT_WRAP BEGIN:VEVENT STATUS:CONFIRMED CLASS:PUBLIC CATEGORIES:Sugar Event VEVENT_WRAP; foreach ($this->properties as $k => $v) { $cal .= $k . ':' . $v . "\n"; } $cal .= 'END:VEVENT' . "\n"; ; return $cal; } /** * converts a Sugar Event into a vEvent * @param object $event * @return */ public function setEvent($event) { $this->event = $event; $this->properties = []; $this->stack = []; $this->properties['CREATED'] = $this->toVCalDate($this->event->date_created); $this->properties['LAST-MODIFIED'] = $this->toVCalDate($this->event->date_modified); $this->properties['DTSTART'] = $this->toVCalDate($this->event->date_start); $this->properties['DTEND'] = $this->toVCalDate($this->event->date_end); $this->properties['SUMMARY'] = $this->event->name; $this->properties['STATUS'] = $this->event->status; $this->properties['UID'] = $this->event->uid; $this->properties['DESCRIPTION'] = $this->event->description; $this->properties['URL'] = $this->event->url; $this->properties['SEQUENCE'] = $this->event->sequence; $this->properties['LOCATION'] = $this->event->location; } /** * Handles the parsing of the vCalendar * @param object $key * @param object $value * */ public function process($key, $value) { $this->properties[$key] = $value; $keys = explode(';', $key); switch ($keys[0]) { case 'BEGIN': $this->event = new Event(); break; case 'END': return false; case 'CREATED': $this->event->date_created = $this->parseDate($key, $value); break; case 'LAST-MODIFIED': $this->event->date_modified = $this->parseDate($key, $value); break; case 'DTSTART': $this->event->date_start = $this->parseDate($key, $value); break; case 'DTEND': $this->event->date_end = $this->parseDate($key, $value); break; case 'UID': $this->event->uid = $value; break; case 'DESCRIPTION': $this->event->description = $value; break; case 'SUMMARY': $this->event->name = $value; break; case 'STATUS': $this->event->status = $value; break; case 'URL': $this->event->url = $value; break; case 'SEQUENCE': $this->event->sequence = $value; break; case 'LOCATION': $this->event->location = $value; break; case 'ATTENDEE': $this->event->attendees[] = $this->parseAttendee($key, $value); break; case 'ORGANIZER': $this->event->organizer = $this->parseAttendee($key, $value); break; } } /** * Parse attendee string into key/value pairs * @param string $key * @param string $value * @return array key/value pairs contanding attendee information */ public function parseAttendee($key, $value) { $attendee = ['email' => str_replace('mailto:', '', $value)]; $attributes = explode(';', $key); foreach ($attributes as $k) { if (substr_count($k, '=') > 0) { $kv = explode('=', $k, 2); $attendee[$kv[0]] = trim($kv[1]); } } return $attendee; } } class vToDo extends vEvent { } class vJournal extends vEvent { } class vFreeBusy extends vEvent { } class vAlarm extends vBasic { } class vTimeZone extends vBasic { public static $timezones = []; protected $section = ''; protected static $winZones = []; /** * Guess custom timezone by name * @param string $tz TZ name * @return DateTimeZone */ public static function guessTimezone($tz) { $windowsZones = null; if (empty(self::$winZones)) { require 'modules/SNIP/windowsZones.php'; self::$winZones = $windowsZones; } if (isset(self::$winZones[$tz])) { return timezone_open(self::$winZones[$tz]); } // TODO: try to find right timezone? return null; } /** * Process TimeZone tags * @param string $key * @param string $value */ public function process($key, $value) { switch ($key) { case 'TZID': self::$timezones[$value] = $this; break; case 'BEGIN': $this->section = $value; return; case 'END': $this->section = ''; return; } $this->properties[$this->section][$key] = $value; } } /** * Calendar events set class */ class vCalendar extends vBasic { public $stack = []; public $properties = []; protected $cur = null; /** * add a SugarEvent to the vCalendar Object * @param SugarEvent $event */ public function addEvent($event) { $ev = new vEvent(); $ev->setEvent($event); $this->stack[] = $ev; } /** * Generates the VCalendar format for this object * @return STRING */ public function generateVCal() { $cal = "BEGIN:VCALENDAR\n"; if (!isset($this->properties['PRODID'])) { $this->properties['PRODID'] = '-//SUGARCRM Inc//SUGARCRM Calendar 6.5//EN'; } if (!isset($this->properties['VERSION'])) { $this->properties['VERSION'] = '2.0'; } if (!isset($this->properties['CALSCALE'])) { $this->properties['CALSCALE'] = 'GREGORIAN'; } foreach ($this->properties as $k => $v) { $cal .= $k . ':' . $v . "\n"; ; } foreach ($this->stack as $item) { $cal .= $item->generateVCal(); } $cal .= 'END:VCALENDAR' . "\n"; ; return $cal; } /** * parses the vCalendar * @param object $key * @param object $value * @return */ public function process($key, $value) { switch ($key) { case 'BEGIN': $parent = $this->cur; switch ($value) { case 'VEVENT': $this->cur = new vEvent(); break; case 'VTIMEZONE': $this->cur = new vTimeZone(); break; case 'VTODO': $this->cur = new vToDo(); break; case 'VJOURNAL': $this->cur = new vJournal(); break; case 'VALARM': $this->cur = new vAlarm(); break; default: if ($this->cur) { $this->cur->process($key, $value); break; } else { throw new Exception('INVALID BEGIN TAG IN VCALENDAR - ' . $key . '=' . $value); } } $this->cur->parent = $parent; break; case 'END': switch ($value) { case 'VEVENT': case 'VTIMEZONE': case 'VTODO': case 'VJOURNAL': case 'VALARM': $this->stack[] = $this->cur; $parent = $this->cur->parent; $this->cur->parent = null; $this->cur = $parent; break; default: if ($this->cur) { $this->cur->process($key, $value); } else { throw new Exception('END TAG IN VCALENDAR WITHOUT BEGIN TAG - ' . $key . '=' . $value); } } break; default: if ($this->cur) { $this->cur->process($key, $value); } else { $this->properties[$key] = $value; } } } } class iCalendar { public $data; /** * parses a STRING represeting an iCalendar (vCalendar 2) * @param STRING $str - iCalendar formatted String * @return */ public function parse($str) { $this->data = []; $lines = explode("\n", $str); $cur = null; for ($i = 0; $i < safeCount($lines); $i++) { $line = trim($lines[$i]); while (!empty($lines[$i + 1]) && substr($lines[$i + 1], 0, 1) == ' ') { $line .= trim(substr($lines[$i + 1], 1)); $i++; } $kv = explode(':', $line, 2); if (safeCount($kv) == 1) { continue; } $key = trim($kv[0]); $value = trim($kv[1]); switch ($key) { case 'END': switch ($value) { case 'VCALENDAR': if ($cur) { $this->data['calendar'][] = $cur; $cur = null; } break; default: if ($cur) { $cur->process($key, $value); } else { throw new Exception('Invalid END Tag - ' . $line); } } break; case 'BEGIN': switch ($value) { case 'VCALENDAR': $cur = new vCalendar(); break; default: if ($cur) { $cur->process($key, $value); } else { throw new Exception('Invalid BEGIN Tag - ' . $line); } } break; default: if ($cur) { $cur->process($key, $value); } else { throw new Exception('Invalid Tag - ' . $line); } } } } /** * Clean up email - uppercase and remove mailto: prefix * @param string $var */ public function cleanEmail($var) { if (strncasecmp($var, 'mailto:', 7) == 0) { return strtoupper(substr($var, 7)); } return strtoupper($var); } /** * Create Sugar meetings from calendar events * @param SugarBean $parent related object to associate with meetings */ public function createSugarEvents($parent = null) { require_once 'modules/Meetings/Meeting.php'; // if email description contains a meeting id that exists, don't create new meeting if (isset($parent) && !empty($parent->description)) { preg_match('/record=(.*)>/', $parent->description, $match); if (!empty($match[1])) { $meeting = new Meeting(); $meeting->disable_row_level_security = true; $prev_meeting = $meeting->retrieve_by_string_fields(['id' => $match[1]]); if (isset($prev_meeting)) { return; } } } foreach ($this->data['calendar'] as $calendar_key => $calendar_val) { foreach ($calendar_val->stack as $key => $val) { if (!$val instanceof vEvent) { continue; } $meeting = BeanFactory::newBean('Meetings'); // Hack - we don't care about this bean's permissions $meeting->disable_row_level_security = true; $prev_seq = 0; $prev_exists = false; $prev_meeting = $meeting->retrieve_by_string_fields(['outlook_id' => $val->event->uid]); if (isset($prev_meeting) && !empty($prev_meeting->id)) { $prev_seq = (int)$prev_meeting->sequence; $prev_exists = true; } if (isset($val->event->status) && $val->event->status == 'CANCELLED' && !$prev_exists) { // can't cancel non-existing event, skip continue; } if (!$prev_exists || ($prev_exists && $val->event->sequence > $prev_seq)) { // insert if doesn't exist, otherwise overwrite it with newer data if current meeting's sequence is greater than prev. sequence if ($parent) { $meeting->assigned_user_id = $parent->assigned_user_id; $meeting->team_set_id = $parent->team_set_id; $meeting->team_id = $parent->team_id; $meeting->parent_id = $parent->id; $meeting->parent_type = $parent->module_dir; } $meeting->date_start = $val->event->date_start; $meeting->date_end = $val->event->date_end; $meeting->name = $val->event->name; $meeting->date_entered = $val->event->date_created; $meeting->date_modified = $val->event->date_modified; $meeting->location = $val->event->location; $dateDiff = strtotime($val->event->date_end) - strtotime($val->event->date_start); $diffHours = floor(($dateDiff) / (60 * 60)); $diffMinutes = ((($dateDiff) % (60 * 60)) / (60 * 60)) * 60; $meeting->duration_hours = $diffHours; $meeting->duration_minutes = $diffMinutes; $meeting->outlook_id = $val->event->uid; $meeting->sequence = $val->event->sequence; if ($prev_exists && $val->event->status == 'CANCELLED') { $meeting->deleted = 1; $meeting->mark_deleted($meeting->id); continue; } $meeting_id = $meeting->save(); // invite people $emails = []; foreach ($val->event->attendees as $attendee) { if (!empty($attendee['email'])) { $emails[] = 'ea.email_address_caps=' . $meeting->db->quoted($this->cleanEmail($attendee['email'])); } } if (!empty($val->event->organizer) && !empty($val->event->organizer['email'])) { $emails[] = 'ea.email_address_caps=' . $meeting->db->quoted($this->cleanEmail($val->event->organizer['email'])); } if (!empty($emails)) { $query = join(' OR ', $emails); $invitees = $meeting->db->query("SELECT eabr.bean_id, eabr.bean_module FROM email_addr_bean_rel eabr JOIN email_addresses ea ON eabr.email_address_id=ea.id WHERE eabr.deleted=0 AND ea.deleted=0 AND eabr.bean_module IN ('Users','Contacts','Leads') AND ($query) "); $ids = array_flip($meeting->relationship_fields); while ($inv = $meeting->db->fetchByAssoc($invitees)) { $module = strtolower($inv['bean_module']); $relname = "meetings_$module"; $linkfields = VardefManager::getLinkFieldForRelationship('Meetings', 'Meeting', $relname); $bean = BeanFactory::newBean($inv['bean_module']); if (empty($bean)) { $GLOBALS['log']->info("createSugarEvents: Don't know how to create bean {$inv['bean_module']}"); continue; } // Hack - we don't care about this bean's permissions $bean->disable_row_level_security = true; $bean->retrieve($inv['bean_id']); $linkname = $linkfields['name']; $meeting->load_relationship($linkname); if (empty($meeting->$linkname)) { $GLOBALS['log']->info("createSugarEvents: Unknown module $module with link $linkname encountered for id {$inv['bean_id']}"); continue; } $GLOBALS['log']->info("Adding link for {$inv['bean_id']} module $module relname $relname linkname $linkname"); $meeting->$linkname->add($bean); } } } } } } }