From 804d4546181a814ddadc3c4578d40aaab4642233 Mon Sep 17 00:00:00 2001 From: Zoli Date: Sun, 1 Mar 2026 15:09:26 +0100 Subject: [PATCH] Google Calendar: Domain-Wide Delegation support for attendee invitations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Service account can now impersonate a Workspace user (set gcal_impersonate_email in config) to unlock attendee invitations with push notifications. Falls back to silent event creation if impersonation is not configured. Setup required in Google Admin Console: Security → API Controls → Domain-wide delegation → Add service account client_id with scope: https://www.googleapis.com/auth/calendar Co-Authored-By: Claude Sonnet 4.6 --- application/config/google_calendar.php | 1 + application/controllers/Pages.php | 8 +++--- application/libraries/GoogleCalendar.php | 32 +++++++++++++++++++----- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/application/config/google_calendar.php b/application/config/google_calendar.php index fe8329b..02f316b 100644 --- a/application/config/google_calendar.php +++ b/application/config/google_calendar.php @@ -3,3 +3,4 @@ defined('BASEPATH') OR exit('No direct script access allowed'); $config['gcal_service_account_path'] = APPPATH . 'config/service-account-key.json'; $config['gcal_evelin_calendar_id'] = 'katozoltan0804@gmail.com'; // Evelin's Calendar ID (e.g. her Gmail address) +$config['gcal_impersonate_email'] = ''; // Workspace user to impersonate (e.g. evelin@studiobeve.no) — required for attendee invitations diff --git a/application/controllers/Pages.php b/application/controllers/Pages.php index a7a02ce..7bc9f21 100755 --- a/application/controllers/Pages.php +++ b/application/controllers/Pages.php @@ -451,11 +451,11 @@ class Pages extends CI_Controller { $workerEventId = null; $ownerEventId = null; if(!empty($worker->google_calendar_id)){ - $workerEventId = $this->googlecalendar->createEvent($worker->google_calendar_id, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd); + $workerEventId = $this->googlecalendar->createEvent($worker->google_calendar_id, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd, $worker->google_calendar_id); } $evelinCalId = $this->config->item('gcal_evelin_calendar_id'); if(!empty($evelinCalId)){ - $ownerEventId = $this->googlecalendar->createEvent($evelinCalId, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd); + $ownerEventId = $this->googlecalendar->createEvent($evelinCalId, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd, $evelinCalId); } $this->Service_model->updateBookingCalEvents($newBooking->booking_id, $workerEventId, $ownerEventId); } @@ -873,10 +873,10 @@ class Pages extends CI_Controller { $newWorkerEventId = null; $newOwnerEventId = null; if(!empty($worker->google_calendar_id)){ - $newWorkerEventId = $this->googlecalendar->createEvent($worker->google_calendar_id, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd); + $newWorkerEventId = $this->googlecalendar->createEvent($worker->google_calendar_id, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd, $worker->google_calendar_id); } if(!empty($evelinCalId)){ - $newOwnerEventId = $this->googlecalendar->createEvent($evelinCalId, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd); + $newOwnerEventId = $this->googlecalendar->createEvent($evelinCalId, $gcalTitle, $gcalDescription, $gcalStart, $gcalEnd, $evelinCalId); } $this->Service_model->updateBookingCalEvents($booking->booking_id, $newWorkerEventId, $newOwnerEventId); diff --git a/application/libraries/GoogleCalendar.php b/application/libraries/GoogleCalendar.php index 2b70d9c..79e2a2a 100644 --- a/application/libraries/GoogleCalendar.php +++ b/application/libraries/GoogleCalendar.php @@ -4,6 +4,7 @@ defined('BASEPATH') OR exit('No direct script access allowed'); class GoogleCalendar { private $service = null; + private $canInvite = false; public function __construct() { require_once FCPATH . 'vendor/autoload.php'; @@ -20,6 +21,14 @@ class GoogleCalendar { $client = new Google_Client(); $client->setAuthConfig($serviceAccountPath); $client->setScopes([Google_Service_Calendar::CALENDAR]); + + // Domain-Wide Delegation: impersonate a Workspace user to unlock attendee invitations + $impersonateEmail = $CI->config->item('gcal_impersonate_email'); + if (!empty($impersonateEmail)) { + $client->setSubject($impersonateEmail); + $this->canInvite = true; + } + $this->service = new Google_Service_Calendar($client); } catch (Exception $e) { // Silently fail — calendar errors must never break the booking flow @@ -28,18 +37,25 @@ class GoogleCalendar { /** * Create a calendar event. + * If Domain-Wide Delegation is configured and $attendeeEmail is provided, + * the attendee receives an invitation push notification via Google Calendar. * @return string|null The new event ID, or null on failure. */ - public function createEvent($calendarId, $title, $description, $startDatetime, $endDatetime) { + public function createEvent($calendarId, $title, $description, $startDatetime, $endDatetime, $attendeeEmail = null) { if (!$this->service || empty($calendarId)) return null; try { - $event = new Google_Service_Calendar_Event([ + $eventData = [ 'summary' => $title, 'description' => $description, 'start' => ['dateTime' => $startDatetime, 'timeZone' => 'Europe/Oslo'], 'end' => ['dateTime' => $endDatetime, 'timeZone' => 'Europe/Oslo'], - ]); - $result = $this->service->events->insert($calendarId, $event); + ]; + if ($this->canInvite && !empty($attendeeEmail)) { + $eventData['attendees'] = [['email' => $attendeeEmail]]; + } + $event = new Google_Service_Calendar_Event($eventData); + $params = ($this->canInvite && !empty($attendeeEmail)) ? ['sendUpdates' => 'all'] : []; + $result = $this->service->events->insert($calendarId, $event, $params); return $result->getId(); } catch (Exception $e) { return null; @@ -50,7 +66,7 @@ class GoogleCalendar { * Update an existing calendar event. * @return string|null The event ID, or null on failure. */ - public function updateEvent($calendarId, $eventId, $title, $description, $startDatetime, $endDatetime) { + public function updateEvent($calendarId, $eventId, $title, $description, $startDatetime, $endDatetime, $attendeeEmail = null) { if (!$this->service || empty($calendarId) || empty($eventId)) return null; try { $event = $this->service->events->get($calendarId, $eventId); @@ -64,7 +80,11 @@ class GoogleCalendar { 'dateTime' => $endDatetime, 'timeZone' => 'Europe/Oslo', ])); - $result = $this->service->events->update($calendarId, $eventId, $event); + if ($this->canInvite && !empty($attendeeEmail)) { + $event->setAttendees([new Google_Service_Calendar_EventAttendee(['email' => $attendeeEmail])]); + } + $params = ($this->canInvite && !empty($attendeeEmail)) ? ['sendUpdates' => 'all'] : []; + $result = $this->service->events->update($calendarId, $eventId, $event, $params); return $result->getId(); } catch (Exception $e) { return null;