salesagility_SuiteCRM/include/GoogleSync/GoogleSyncBase.php

993 lines
41 KiB
PHP
Executable File

<?php
/**
*
* 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".
*/
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}
require_once __DIR__ . '/../../modules/Users/User.php';
require_once __DIR__ . '/../../modules/Meetings/Meeting.php';
require_once __DIR__ . '/GoogleSyncExceptions.php';
use SuiteCRM\Utility\SuiteValidator;
/**
* Implements Google Calendar Syncing
*
* @license https://raw.githubusercontent.com/salesagility/SuiteCRM/master/LICENSE.txt
* GNU Affero General Public License version 3
* @author Benjamin Long <ben@offsite.guru>
*/
#[\AllowDynamicProperties]
class GoogleSyncBase
{
/** @var User The SuiteCRM User Bean we're currently working with */
protected $workingUser;
/** @var \Google\Client The Google client object for the current sync job */
protected $gClient;
/** @var \Google\Service\Calendar The Google Calendar Service Object */
protected $gService;
/** @var array The Google AuthcConfig json */
protected $authJson = array();
/** @var string The local timezone */
protected $timezone;
/** @var string The Calendar ID */
protected $calendarId;
/** @var array An array of SuiteCRM meeting id's that we've already synced this session */
protected $syncedList = array();
/** @var object A Database Instance */
protected $db;
/** @var object A Logger Instance */
protected $logger;
/** @var string The name of the Google Calendar to use */
protected $suiteCalendarName = 'SuiteCRM';
/**
* Class Constructor
*
* @param array $sugarConfig - using $sugar_config as a dependency
*/
public function __construct($sugarConfig)
{
$this->logger = LoggerManager::getLogger();
$this->timezone = date_default_timezone_get(); // This defaults to the server timezone. Overridden later.
$this->authJson = $this->getAuthJson($sugarConfig);
$this->db = DBManagerFactory::getInstance();
$this->logger->debug(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . '__construct');
if(!empty($sugarConfig['google_calendar_sync_name'])){
$this->suiteCalendarName = $sugarConfig['google_calendar_sync_name'];
}
}
/**
* Class Destructor
*/
public function __destruct()
{
// Set the log level back to the original value
$this->logger->debug(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . '__destruct');
}
/**
* Gets the auth json string from the system
*
* @param array $sugarConfig
* @return array|false json on success, false on failure
*/
protected function getAuthJson($sugarConfig)
{
if (empty($sugarConfig['google_auth_json'])) {
return false;
}
$authJson_local = json_decode(base64_decode($sugarConfig['google_auth_json']), true);
if (!$authJson_local) {
// The authconfig json string is invalid json
throw new GoogleSyncException('google_auth_json not vaild json', GoogleSyncException::JSON_CORRUPT);
} elseif (!array_key_exists('web', $authJson_local)) {
// The authconfig is valid json, but the 'web' key is missing. This is not a valid authconfig.
throw new GoogleSyncException('google_auth_json missing web key', GoogleSyncException::JSON_KEY_MISSING);
}
return $authJson_local;
}
/**
* Creates and Sets the Google client in the object
*
* @param string $id : the SuiteCRM user id
*
* @return bool Success/Failure
* @throws GoogleSyncException
*/
protected function setClient($id)
{
$isValidator = new SuiteValidator();
if (!$isValidator->isValidId($id)) {
throw new GoogleSyncException('Google Sync Base trying to set Client but given an invalid ID: ' . $id, GoogleSyncException::INVALID_CLIENT_ID);
}
if (!$gClient_local = $this->getClient($id)) {
return false;
}
$this->gClient = $gClient_local;
return true;
}
/**
* Set the Google client up for the user by id
*
* @param string $id : the SuiteCRM user id
*
* @return \Google\Client|false Google\Client on success. False on failure.
* @throws GoogleSyncException if user invalid, unable to retrive the user, or json error
*/
protected function getClient($id)
{
$isValidator = new SuiteValidator();
if (!$isValidator->isValidId($id)) {
throw new GoogleSyncException('GoogleSyncBase trying to get Client but given ID is invalid: ' . $id, GoogleSyncException::INVALID_CLIENT_ID);
}
// Retrieve user bean
if (!isset($this->workingUser)) {
$this->workingUser = BeanFactory::getBean('Users', $id);
if (!$this->workingUser) {
throw new GoogleSyncException('Unable to retrieve a User bean', GoogleSyncException::UNABLE_TO_RETRIEVE_USER);
}
}
// Retrieve Access Token JSON from user preference
$accessToken = json_decode(base64_decode($this->workingUser->getPreference('GoogleApiToken', 'GoogleSync')), true);
if (!array_key_exists('access_token', $accessToken)) {
// The Token is invalid JSON or missing
throw new GoogleSyncException('GoogleApiToken missing access_token key', GoogleSyncException::JSON_KEY_MISSING);
}
// The refresh token is only provided once, on first authentication. It must be added afterwards.
if (!array_key_exists('refresh_token', $accessToken)) {
$accessToken['refresh_token'] = base64_decode($this->workingUser->getPreference('GoogleApiRefreshToken', 'GoogleSync'));
}
// New Google Client and refresh the token if needed
$client = $this->getGoogleClient($accessToken);
if (!$client) {
return false;
}
return $client;
}
/**
* New Google Client and refresh the token if needed
*
* @param array $accessToken
* @return \Google\Client or false on Exception
* @throws GoogleSyncException If the refresh token is missing
* @throws Exception rethrows if caught from Google\Client::fetchAccessTokenWithRefreshToken
*/
protected function getGoogleClient($accessToken)
{
if (empty($accessToken)) {
throw new GoogleSyncException('Access Token Parameter Missing', GoogleSyncException::ACCSESS_TOKEN_PARAMETER_MISSING);
}
// New Google Client
$client = new \Google\Client();
$client->setApplicationName('SuiteCRM');
$client->setScopes(Google\Service\Calendar::CALENDAR);
$client->setAccessType('offline');
$client->setAuthConfig($this->authJson);
$client->setAccessToken($accessToken);
// Refresh the token if needed
if ($client->isAccessTokenExpired()) {
$this->logger->info(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . 'Refreshing Access Token');
$refreshToken = $client->getRefreshToken();
if (!empty($refreshToken)) {
$client->fetchAccessTokenWithRefreshToken($refreshToken);
// Save new token to user preference
$this->workingUser->setPreference('GoogleApiToken', base64_encode(json_encode($client->getAccessToken())), 'GoogleSync');
$this->workingUser->savePreferencesToDB();
} elseif (empty($refreshToken)) {
throw new GoogleSyncException('Refresh token is missing', GoogleSyncException::NO_REFRESH_TOKEN);
}
}
return $client;
}
/**
* Initialize Service for User
*
* @param string $id The SuiteCRM user id
*
* @return bool Success/Failure
* @throws GoogleSyncException if $id is invalid
* @throws GoogleSyncException if unable to retrive the user
* @throws GoogleSyncException if Google Client fails to set up
* @throws GoogleSyncException if timezone set fails
* @throws GoogleSyncException if unable to setup Google Service
* @throws GoogleSyncException if unable to setup Google Calendar Id
*/
protected function initUserService($id)
{
$isValidator = new SuiteValidator();
if (!$isValidator->isValidId($this->db->quote($id))) {
throw new GoogleSyncException('Invalid ID requested in initUserService', GoogleSyncException::INVALID_CLIENT_ID);
}
// Retrieve user bean
$this->workingUser = null;
$this->workingUser = BeanFactory::getBean('Users', $id);
if (!$this->workingUser) {
throw new GoogleSyncException('Unable to retrieve a User bean', GoogleSyncException::UNABLE_TO_RETRIEVE_USER);
}
if (!$this->setClient($id)) {
throw new GoogleSyncException('Unable to setup Google Client', GoogleSyncException::UNABLE_TO_SETUP_GCLIENT);
}
$this->setTimezone($this->workingUser->getPreference('timezone', 'global'));
if (!$this->setGService()) {
throw new GoogleSyncException('Unable to setup Google Service', GoogleSyncException::GSERVICE_FAILURE);
}
if (!$this->setUsersGoogleCalendar()) {
throw new GoogleSyncException('Unable to setup Google Calendar Id', GoogleSyncException::GCALENDAR_FAILURE);
}
return true;
}
/**
* Retrieve List of meetings owned by the Current Working User
*
*
* @return array Array of SuiteCRM Meeting Beans
* @throws GoogleSyncException if $this->workingUser->id is invalid
* @throws GoogleSyncException if unable to get Meetings bean
*/
protected function getUserMeetings($userId)
{
$this->logger->info(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . 'Id ' . $userId);
// Validate the workingUser id
$isValidator = new SuiteValidator();
if (!$isValidator->isValidId($userId)) {
throw new GoogleSyncException('Invalid ID requested in getUserMeetings', GoogleSyncException::INVALID_USER_ID);
}
// We do it this way so we also get deleted meetings
$query = "SELECT id FROM meetings WHERE assigned_user_id = '" . $userId . "' AND date_start <= now() + interval 3 month";
$result = $this->db->query($query);
$meetings = array();
while ($row = $this->db->fetchByAssoc($result)) {
$meeting = BeanFactory::getBean('Meetings');
if (!$meeting) {
throw new GoogleSyncException('Unable to get Meetings bean.', GoogleSyncException::UNABLE_TO_RETRIEVE_MEETING);
}
$meeting->retrieve($row['id'], true, false);
$meetings[] = $meeting;
}
return $meetings;
}
/**
* Set user's google calendar id
*
*
* @return bool|int Success/Failure calendar ID if success
* @throws GoogleSyncException if $this->workingUser is not a user bean
* @throws GoogleSyncException if $this->workingUser->id is invalid
*/
protected function setUsersGoogleCalendar()
{
// Make sure we have a Google Calendar Service instance
if (!$this->isServiceExists()) {
return false;
}
// Make sure the user bean is a User instance
if (!$this->workingUser instanceof User) {
throw new GoogleSyncException('GoogleSyncBase is trying to setUsersGoogleCalendar but workingUser type is incorrect, ' . gettype($this->workingUser) . ' given.', GoogleSyncException::INCORRECT_WORKING_USER_TYPE);
}
// Make sure the User bean ID is valid
$isValidator = new SuiteValidator();
if (!$isValidator->isValidId($this->db->quote($this->workingUser->id))) {
throw new GoogleSyncException('Invalid ID requested in setUsersGoogleCalendar', GoogleSyncException::INVALID_USER_ID);
}
// get list of users calendars
$calendarList = $this->gService->calendarList->listCalendarList();
// find the id of the 'SuiteCRM' calendar ... in the future, this will set the calendar of the users choosing.
$this->calendarId = $this->getSuiteCRMCalendar($calendarList);
// if the SuiteCRM calendar doesn't exist... Wipe the users current sync data, and create it!
if (!$this->isCalendarExists()) {
$this->logger->info(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . 'Unable to find the SuiteCRM Google Calendar, wiping current sync data & creating it!');
$helper = new GoogleSyncHelper;
$helper->wipeLocalSyncData($this->workingUser->id);
$calendar = new \Google\Service\Calendar\Calendar();
$calendar->setSummary($this->suiteCalendarName);
$calendar->setTimeZone($this->timezone);
$createdCalendar = $this->gService->calendars->insert($calendar);
$this->calendarId = $createdCalendar->getId();
}
// Final check to make sure we have an ID
if (!$this->isCalendarExists()) {
throw new GoogleSyncException('Unable to set Google Calendar', GoogleSyncException::GCALENDAR_FAILURE);
}
return $this->calendarId;
}
/**
* find the id of the 'SuiteCRM' calendar ... in the future, this will return the calendar of the users choosing.
*
* @param \Google\Service\Calendar\CalendarList $calendarList
*
* @return string|null Matching Google Calendar ID or null.
*/
protected function getSuiteCRMCalendar(Google\Service\Calendar\CalendarList $calendarList)
{
foreach ($calendarList->getItems() as $calendarListEntry) {
if ($calendarListEntry->getSummary() === $this->suiteCalendarName) {
return $calendarListEntry->getId();
break;
}
}
return null;
}
/**
* Get events in users google calendar
*
*
* @return bool|array Array of Google\Service\Calendar\Event Objects
*/
protected function getUserGoogleEvents()
{
// Make sure we have a Google Calendar Service instance and
// Make sure we have a calendar id
if (!$this->isServiceExists() || !$this->isCalendarExists()) {
return false;
}
// Set Options for what events we get from Google
$optParams = array(
'maxResults' => 500,
'showDeleted' => true,
'singleEvents' => true,
'timeMin' => date('c', strtotime('-1 month')),
'timeMax' => date('c', strtotime('+3 month')),
);
$results_g = $this->gService->events->listEvents($this->calendarId, $optParams);
// We only want the events, not the leading cruft
$results = $results_g->getItems();
if (empty($results)) {
$this->logger->info(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . 'No events found.');
} else {
$this->logger->info(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . 'Found ' . (is_countable($results) ? count($results) : 0) . ' Google Events');
}
return $results;
}
/**
* Make sure we have a Google Calendar Service instance
*
* @return boolean
*/
protected function isServiceExists()
{
// Make sure we have a Google Calendar Service instance
if (!isset($this->gService)) {
return false;
}
return true;
}
/**
* Make sure we have a calendar id
*
* @return boolean
*/
protected function isCalendarExists()
{
// Make sure we have a calendar id
if (empty($this->calendarId)) {
return false;
}
return true;
}
/**
* Get a google event by the event id
*
* @param string $event_id Google Event ID
*
* @return \Google\Service\Calendar\Event|null Google\Service\Calendar\Event if found, null if not found
* @throws GoogleSyncException if $event_id is empty
* @throws GoogleSyncException if Google Service not set up
*/
protected function getGoogleEventById($event_id)
{
if (empty($event_id)) {
// If we didn't get passed an event id, throw an exception
throw new GoogleSyncException('event ID is empty', GoogleSyncException::EVENT_ID_IS_EMPTY);
}
// Make sure the calendar service is set up
if (!$this->isServiceExists()) {
throw new GoogleSyncException('Cannot Continue Without Google Service', GoogleSyncException::GCALENDAR_FAILURE);
}
$gEvent = $this->gService->events->get($this->calendarId, $event_id);
if (!empty($gEvent->getId())) {
return $gEvent;
}
return null;
}
/**
* Get a SuiteCRM meeting by Google Event ID
*
* @param string $event_id The Google Event ID
*
* @return \Meeting|null SuiteCRM Meeting Bean if found, null if not found
* @throws GoogleSyncException if more than one meeting matches $event_id
* @throws GoogleSyncException If unable to retrieve meeting bean
*/
protected function getMeetingByEventId($event_id)
{
// We do it this way so we also get deleted meetings
$eventIdQuoted = $this->db->quoted($event_id);
$query = "SELECT id FROM meetings WHERE gsync_id = {$eventIdQuoted}";
$result = $this->db->query($query);
if (!$result) {
throw new GoogleSyncException('Meeting not found with specified gsync_id: ' . $eventIdQuoted, GoogleSyncException::MEETING_NOT_FOUND);
}
// This checks to make sure we only get one result. If we get more than one, something is inconsistant in the DB
if ($result->num_rows > 1) {
throw new GoogleSyncException('More than one meeting matches Google Id!', GoogleSyncException::AMBIGUOUS_MEETING_ID);
} elseif ($result->num_rows == 0) {
return null; // No matches Found
}
$row = $this->db->fetchByAssoc($result);
$meeting = BeanFactory::getBean('Meetings', $row['id']);
if (!$meeting) {
throw new GoogleSyncException('Unable to get Meetings bean.', GoogleSyncException::UNABLE_TO_RETRIEVE_MEETING);
}
return $meeting;
}
/**
* Creates and Sets $this->gService to a valid Google Calendar Service
*
* @return bool true on Success
* @throws GoogleSyncException If gClient not set
* @throws GoogleSyncException If gService Setup Fails
*/
protected function setGService()
{
// make sure we have a client set
if (!isset($this->gClient)) {
throw new GoogleSyncException('The Google Client is not set up. See setClient Method', GoogleSyncException::NO_GCLIENT_SET);
}
// create new calendar service
$this->gService = new \Google\Service\Calendar($this->gClient);
if ($this->isServiceExists()) {
return true;
}
throw new GoogleSyncException('Setting $this->gService Failed', GoogleSyncException::NO_GSERVICE_SET);
}
/**
* Push event from SuiteCRM to Google Calendar
*
* If the google event is not provided, a new one will be created
* and inserted. If one is provided, the existing Google Event will
* be updated.
*
* @param Meeting|null $event_local : SuiteCRM Meeting Bean
* @param \Google\Service\Calendar\Event|null $event_remote (optional) \Google\Service\Calendar\Event Object
*
* @return string|bool Meeting Id on success, false on failure
*/
protected function pushEvent(Meeting $event_local = null, Google\Service\Calendar\Event $event_remote = null)
{
if (!$event_local instanceof Meeting) {
throw new InvalidArgumentException('Argument 1 passed to GoogleSyncBase::pushEvent() must be an instance of Meeting, ' . getType($event_local) . ' given.');
}
if (!$this->gService instanceof Google\Service\Calendar) {
throw new GoogleSyncException('GooleSyncBase is trying to push event but Google\Service\Calendar\Resource\Events is not set.', GoogleSyncException::NO_GSERVICE_SET);
}
if (!$this->gService->events instanceof Google\Service\Calendar\Resource\Events) {
throw new GoogleSyncException('GooleSyncBase is trying to push event but Google\Service\Calendar\Resource\Events is not set.', GoogleSyncException::NO_GRESOURCE_SET);
}
if (!isset($event_remote) || empty($event_remote)) {
$event = $this->createGoogleCalendarEvent($event_local);
$return = $this->gService->events->insert($this->calendarId, $event);
} elseif (isset($event_remote)) {
$event = $this->updateGoogleCalendarEvent($event_local, $event_remote);
$return = $this->gService->events->update($this->calendarId, $event->getId(), $event);
}
/* We don't get a status code back showing success. Instead, the return of the
* create or update is the Google\Service\Calendar\Event object after saving.
* So we check to make sure it has an ID to determine Success/Failure.
*/
if (!isset($return->id)) {
throw new GoogleSyncException('GCalendar insert/update failed.', GoogleSyncException::GEVENT_INSERT_OR_UPDATE_FAILURE);
}
// Set the SuiteCRM Meeting's last sync timestamp, and google id. Return the saved meeting id from called method.
return $this->setLastSync($event_local, $return->getId());
}
/**
* Helper method to get a Google\Service\Calendar\EventExtendedProperties object for the Google event
*
* Takes the local and remote events, and returns a Google\Service\Calendar\EventExtendedProperties
*
* @param \Google\Service\Calendar\Event $event_remote \Google\Service\Calendar\Event Object
* @param Meeting $event_local Meeting (optional) \Meeting Bean
*
* @return Google\Service\Calendar\EventExtendedProperties object
*/
protected function returnExtendedProperties(Google\Service\Calendar\Event $event_remote, Meeting $event_local)
{
// We pull the existing extendedProperties, and change our values
// That way we don't mess with anything else that's using other values.
$extendedProperties = $event_remote->getExtendedProperties();
if (!empty($extendedProperties)) {
$private = $extendedProperties->getPrivate();
} elseif (empty($extendedProperties)) {
$extendedProperties = new Google\Service\Calendar\EventExtendedProperties;
$private = array();
}
$private['suitecrm_id'] = $event_local->id;
$private['suitecrm_type'] = $event_local->module_name;
$extendedProperties->setPrivate($private);
return $extendedProperties;
}
/**
* Pull event from Google Calendar to SuiteCRM
*
* If the SuiteCRM Meeting is not provided, a new one will be created
* and inserted. If one is provided, the existing meeting will be updated.
*
* @param \Google\Service\Calendar\Event|null $event_remote \Google\Service\Calendar\Event Object
* @param Meeting|null $event_local Meeting (optional) \Meeting Bean
*
* @return bool Success/Failure of setLastSync, since that's what saves the record
* @throws GoogleSyncException if returned event invalid
*/
protected function pullEvent(Google\Service\Calendar\Event $event_remote = null, Meeting $event_local = null)
{
if (!$event_remote instanceof Google\Service\Calendar\Event) {
throw new InvalidArgumentException('Argument 1 passed to GoogleSyncBase::pullEvent() must be an instance of Google\Service\Calendar\Event, ' . getType($event_local) . ' given.');
}
if (!isset($event_local) || empty($event_local)) {
$event = $this->createSuitecrmMeetingEvent($event_remote);
} elseif (isset($event_local)) {
$event = $this->updateSuitecrmMeetingEvent($event_local, $event_remote);
}
if (empty($event)) {
throw new GoogleSyncException('Something Horrible Happened in [create|update]SuitecrmMeetingEvent!', GoogleSyncException::MEETING_CREATE_OR_UPDATE_FAILURE);
}
// We need to set the suitecrm_ private properties in the Google event here,
// Otherwise it's seen as a new event next time we sync
$extendedProperties = $this->returnExtendedProperties($event_remote, $event);
$event_remote->setExtendedProperties($extendedProperties);
$greturn = $this->gService->events->update($this->calendarId, $event_remote->getId(), $event_remote);
/* We don't get a status code back showing success. Instead, the return of the
* create or update is the Google\Service\Calendar\Event object after saving.
* So we check to make sure it has an ID to determine Success/Failure.
*/
if (isset($greturn->id)) {
return $this->setLastSync($event, $event_remote->getId());
}
return false;
}
/**
* Delete SuiteCRM Meeting
*
* @param Meeting|null $meeting SuiteCRM Meeting Bean
*
* @return string|bool Meeting Id on success, false on failure (from setLastSync, since that's what saves the record)
*/
protected function delMeeting(Meeting $meeting = null)
{
if (!$meeting instanceof Meeting) {
throw new InvalidArgumentException('Argument 1 passed to GoogleSyncBase::delMeeting() must be an instance of Meeting, ' . getType($meeting) . ' given.');
}
$meeting->deleted = '1';
$meeting->gsync_id = '';
return $this->setLastSync($meeting);
}
/**
* Delete Google Event
*
* @param \Google\Service\Calendar\Event|null $event \Google\Service\Calendar\Event Object
* @param String $meeting_id SuiteCRM Meeting Id
*
* @return string Meeting Id on success
* @throws GoogleSyncException If Google Service Unset
* @throws GoogleSyncException If Meeting ID missing
* @throws GoogleSyncException If Meeting ID fails validation
* @throws GoogleSyncException If delete fails
*/
protected function delEvent(Google\Service\Calendar\Event $event = null, $meeting_id = null)
{
if (!$event instanceof Google\Service\Calendar\Event) {
throw new InvalidArgumentException('Argument 1 passed to GoogleSyncBase::delEvent() must be an instance of Google\Service\Calendar\Event, ' . gettype($event) . ' given');
}
// Make sure the calendar service is set up
if (!$this->isServiceExists()) {
throw new GoogleSyncException('The Google Service is not set up. See setGService Method.', GoogleSyncException::NO_GSERVICE_SET);
}
// Make sure we got a meeting_id
if (!$meeting_id) {
throw new GoogleSyncException('This method requires a meeting id as the 2nd parameter', GoogleSyncException::MEETING_ID_IS_EMPTY);
}
// Validate and quote the meetingID
$isValidator = new SuiteValidator();
if (!$isValidator->isValidId($this->db->quote($meeting_id))) {
throw new GoogleSyncException('Meeting ID could not be validated', GoogleSyncException::RECORD_VALIDATION_FAILURE);
}
$return = $this->gService->events->delete($this->calendarId, $event->getId());
// Pull the status code returned to determine Success/Failure
$statusCode = $return->getStatusCode();
if ($statusCode >= 200 && $statusCode <= 299) {
// 2xx statusCode = success
$this->logger->debug(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . 'Received Success Status Code: ' . $statusCode . ' on delete.');
// This removes the gsync_id reference from the table.
$sql = "UPDATE meetings SET gsync_id = '' WHERE id = {$this->db->quoted($meeting_id)}";
$res = $this->db->query($sql);
if (!$res) {
$this->logger->fatal(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . 'Failed to remove gsync_id from record ' . $meeting_id);
}
return $meeting_id;
}
throw new GoogleSyncException('Received Failure Status Code: ' . $statusCode . ' on delete!', GoogleSyncException::GEVENT_INSERT_OR_UPDATE_FAILURE);
}
/**
* Helper method. Clear all popup reminders from crm meeting
*
* @param string $event_id The ID of the event in the DB
*
* @return bool Success/Failure
*/
protected function clearPopups($event_id)
{
if (!isset($event_id) || empty($event_id)) {
throw new InvalidArgumentException('Argument 1 not passed to GoogleSyncBase::clearPopups()');
}
// Disable all popup reminders for the SuiteCRM meeting, and mark reminders where email is disabled as deleted.
$eventIdQuoted = $this->db->quoted($event_id);
$sql = sprintf("UPDATE reminders SET popup = '0', deleted = CASE WHEN email = '0' THEN '1' ELSE deleted END WHERE related_event_module_id = %s AND deleted = '0'", $eventIdQuoted);
$res = $this->db->query($sql);
if (!$res) {
throw new GoogleSyncException('SQL Failure', GoogleSyncException::SQL_FAILURE);
}
return true;
}
/**
* Update SuiteCRM Meeting from Google Calendar Event
*
* @param Meeting $event_local SuiteCRM Meeting Bean
* @param \Google\Service\Calendar\Event $event_remote Google\Service\Calendar\Event Object
*
* @return Meeting|bool SuiteCRM Meeting Bean or false on failure
* @throws GoogleSyncException if the Google Event is missing required data
*/
protected function updateSuitecrmMeetingEvent(Meeting $event_local, Google\Service\Calendar\Event $event_remote)
{
$event_local->name = (string) $event_remote->getSummary();
if (empty($event_local->name)) { // Google doesn't require titles on events.
$event_local->name = '(No title)'; // This is what they look like in google, so it should be seamless.
}
$event_local->description = (string) $event_remote->getDescription();
$event_local->location = (string) $event_remote->getLocation();
// Get Start/End/Duration from Google Event
// FUTURE: This is where all day event conversion will need to happen.
$start = $event_remote->getStart();
if (!$start) {
throw new GoogleSyncException(
'GoogleSyncBase is trying to get "start" as Google\Service\Calendar\EventDateTime but it is not set',
GoogleSyncException::NO_REMOVE_EVENT_START_IS_NOT_SET
);
}
if (!$start instanceof Google\Service\Calendar\EventDateTime) {
throw new GoogleSyncException(
'GoogleSyncBase is trying to get "start" as Google\Service\Calendar\EventDateTime but it is incorrect, ' .
gettype($start) . ' given.',
GoogleSyncException::NO_REMOVE_EVENT_START_IS_INCORRECT
);
}
$starttime = strtotime($start->getDateTime());
$endtime = strtotime($event_remote->getEnd()->getDateTime());
if (!$starttime || !$endtime) { // Verify we have valid time objects (All day events will fail here.)
throw new GoogleSyncException(
'Unable to retrieve times from Google Event',
GoogleSyncException::GOOGLE_RECORD_PARSE_FAILURE
);
}
$diff = abs($starttime - $endtime);
$tmins = $diff / 60;
$hours = floor($tmins / 60);
$mins = $tmins % 60;
// Set Start/End/Duration in SuiteCRM Meeting and Assigned User
$event_local->date_start = gmdate("Y-m-d H:i:s", $starttime);
$event_local->date_end = gmdate("Y-m-d H:i:s", $endtime);
$event_local->duration_hours = $hours;
$event_local->duration_minutes = $mins;
$event_local->assigned_user_id = $this->workingUser->id;
// Disable all popup reminders for the SuiteCRM meeting. We add them back from Google event below.
$event_id = $event_local->id;
$this->clearPopups($event_id);
// Get Google Event Popup Reminders
$gReminders = $event_remote->getReminders();
$overrides = $gReminders->getOverrides();
// Create a new popup reminder for each google reminder
$helper = new GoogleSyncHelper;
$nestedArray = $helper->createSuitecrmReminders($overrides, $event_local);
$reminders = $nestedArray[0];
$invitees = $nestedArray[1];
foreach ($reminders as $reminder) {
$reminder->save(false);
}
foreach ($invitees as $invitee) {
$invitee->save(false);
}
return $event_local;
}
/**
* Create SuiteCRM Meeting event
*
* @param \Google\Service\Calendar\Event $event_remote The Google\Service\Calendar\Event we're creating a SuiteCRM Meeting for
*
* @return Meeting|bool SuiteCRM Meeting Bean or false on failure
* @throws GoogleSyncException if fails to retrive meeting
*/
protected function createSuitecrmMeetingEvent(Google\Service\Calendar\Event $event_remote)
{
$this->logger->debug(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . 'Creating New SuiteCRM Meeting');
$meeting = BeanFactory::getBean('Meetings');
if (!$meeting) {
throw new GoogleSyncException('Unable to get Meeting bean.', GoogleSyncException::UNABLE_TO_RETRIEVE_MEETING);
}
$meeting->id = create_guid();
$meeting->new_with_id = true;
$event_local = $this->updateSuitecrmMeetingEvent($meeting, $event_remote);
return $event_local;
}
/**
* Update Google Calendar Event from SuiteCRM Meeting
*
* @param Meeting $event_local SuiteCRM Meeting Bean
* @param \Google\Service\Calendar\Event $event_remote Google Event Object
*
* @return \Google\Service\Calendar\Event
*/
protected function updateGoogleCalendarEvent(Meeting $event_local, Google\Service\Calendar\Event $event_remote)
{
$event_remote->setSummary($event_local->name);
$event_remote->setDescription($event_local->description);
$event_remote->setLocation($event_local->location);
$timedate = new TimeDate;
$localStart = $timedate->to_db($event_local->date_start, false);
$localEnd = $timedate->to_db($event_local->date_end, false);
$startDateTime = new \Google\Service\Calendar\EventDateTime;
$startDateTime->setDateTime(date(DATE_ATOM, strtotime($localStart . ' UTC')));
$startDateTime->setTimeZone($this->timezone);
$event_remote->setStart($startDateTime);
$endDateTime = new \Google\Service\Calendar\EventDateTime;
$endDateTime->setDateTime(date(DATE_ATOM, strtotime($localEnd . ' UTC')));
$endDateTime->setTimeZone($this->timezone);
$event_remote->setEnd($endDateTime);
$extendedProperties = $this->returnExtendedProperties($event_remote, $event_local);
$event_remote->setExtendedProperties($extendedProperties);
// Copy over popup reminders
$event_local_id = $this->db->quoted($event_local->id);
$reminders_local = BeanFactory::getBean('Reminders')->get_full_list(
"",
"reminders.related_event_module = 'Meetings'" .
" AND reminders.related_event_module_id = $event_local_id" .
" AND popup = '1'"
);
if ($reminders_local) {
$reminders_remote = new Google\Service\Calendar\EventReminders;
$reminders_remote->setUseDefault(false);
$reminders_array = array();
foreach ($reminders_local as $reminder_local) {
$reminder_remote = new Google\Service\Calendar\EventReminder;
$reminder_remote->setMethod('popup');
$reminder_remote->setMinutes($reminder_local->timer_popup / 60);
$reminders_array[] = $reminder_remote;
}
$reminders_remote->setOverrides($reminders_array);
$event_remote->setReminders($reminders_remote);
}
return $event_remote;
}
/**
* Create New Google Event object for SuiteCRM Meeting
*
* @param Meeting $event_local SuiteCRM Meeting Bean
*
* @return \Google\Service\Calendar\Event Google\Service\Calendar\Event Object
*/
protected function createGoogleCalendarEvent(Meeting $event_local)
{
//We're creating a new event
$event_remote_empty = new Google\Service\Calendar\Event;
$extendedProperties = new Google\Service\Calendar\EventExtendedProperties;
$extendedProperties->setPrivate(array());
$event_remote_empty->setExtendedProperties($extendedProperties);
//Set the Google Event up to match the SuiteCRM one
$event_remote = $this->updateGoogleCalendarEvent($event_local, $event_remote_empty);
return $event_remote;
}
/**
* Set the timezone
*
* @param string $timezone : timezone_identifier (ie. America/New_York)
*
* @return bool true on Success
* @throws GoogleSyncException on failure
*/
protected function setTimezone($timezone)
{
if (date_default_timezone_set($timezone)) {
$this->timezone = date_default_timezone_get();
$this->logger->info(__FILE__ . ':' . __LINE__ . ' ' . __METHOD__ . ' - ' . 'Timezone set to \'' . $this->timezone . '\'');
return true;
}
throw new GoogleSyncException('Failed to set timezone to ' . $timezone, GoogleSyncException::TIMEZONE_SET_FAILURE);
}
/**
* Set the last sync time for the record
*
* This *must* be called *after* the sync is done
* This also saves the event, so you don't need to do it twice. Just call this.
*
* @param Meeting $event_local SuiteCRM Meeting bean
* @param string $gEventId (optional) The ID that Google has for the event.
*
* @return string Meeting Id on success
* @throws GoogleSyncException If working user invalid
* @throws GoogleSyncException if meeting save fails
*/
protected function setLastSync(Meeting $event_local, $gEventId = null)
{
if (isset($gEventId)) {
$event_local->gsync_id = $gEventId;
}
$event_local->gsync_lastsync = time() + 3; // we add three seconds to this, so that the modified time is always older than the gsync_lastsync time
$return = $event_local->save(false);
$isValidator = new SuiteValidator();
if ($isValidator->isValidId($return)) {
if (!$this->workingUser instanceof User) {
throw new GoogleSyncException('GoogleSyncBase is trying to setLastSync but workingUser type is incorrect, ' . gettype($this->workingUser) . ' given.', GoogleSyncException::INCORRECT_WORKING_USER_TYPE);
}
$event_local->set_accept_status($this->workingUser, 'accept'); // Set the meeting as accepted by the user, otherwise it doesn't show up on the calendar. We do it here because it must be saved first.
//$this->syncedList[] = $event_local->id;
return $event_local->id;
}
throw new GoogleSyncException('Something went wrong saving the local record.', GoogleSyncException::MEETING_SAVE_FAILURE);
}
}