Google Calendar Integration & Sync Issues

Using v8.8.0 and trying to get the google calendar sync to work. It took a few tries to get the google api intgration to stick under Administration / Google Sync Settings; meaning that I uploaded the json file and saved and logged out and in again just to find that it dropped the json file. But, seems to stick now.

Next logged with user account and had the same experience; first didn’t seem to stick but after a few tries it seems that the Google Account Synchronization sticks and indicates that it’s been configured.

But, still it doesn’t seem to sync with the personal google calendar tied to the account and yes the calendar sync option is marked. And everything indicates Configured.

What is it that I’m missing out here? Also, once working, does this also mean that when scheduling a meeting that it will create a Google Meeting link as well?

For me it hasn’t worked properly in years. It seems to work once I ā€œre-authorizeā€ and it syncs once, but subsequent syncs never work. I’ve tried to debug a few times but just end up re-authorizing when I need a sync a couple of times a day.

Ok, sounds like one would need to look for third-party add-on to solve this then. Pitty, since it’s an essential function of a crm which is crazy that it doesn’t work.

While on the topic of integration; what about google maps integration; is this also something which is an issue as well from your side to get to work?

I’m experiencing the same critical issue with Google Calendar sync in SuiteCRM 8.8 on PHP 8.3, and I want to clarify that this is not related to scheduler misconfiguration — the scheduled sync is running perfectly every minute.

The core issue:

The OAuth access token is being refreshed successfully and appears to be saved using setPreference() and savePreferencesToDB(). But despite this, every subsequent run treats the token as expired, resulting in the same cycle:

  • Scheduler runs and loads the access token from user preferences
  • isAccessTokenExpired() returns true
  • A new token is fetched and logged as successfully saved
  • On the next run, it repeats — never reusing the token just saved

What I’ve discovered:

This isn’t just a logic bug — it appears to be tied to SuiteCRM’s session behavior, particularly in CLI (cron) context:

  • Either the token isn’t actually saved to the DB despite the success log, or
  • The session data ($_SESSION[...]) used to track preference state is reset or not properly restored when cron runs again

So even though setPreference() sets the value and savePreferencesToDB() reports success, the system still loads an outdated or blank token on the next run. This suggests the refreshed token isn’t truly being persisted or is being lost between runs — possibly because SuiteCRM stores preferences in session and relies on session state even in CLI mode.

Why this matters:

This did not happen in SuiteCRM 8.3 under the same environment. It only started after upgrading to 8.8. So this looks like a regression, possibly due to session changes or stricter behavior introduced in 8.8 or triggered by PHP 8.3.

Suggestion:

The OAuth logic needs to force-load preferences from the database explicitly during cron execution and not rely on session state to persist values between runs.

@guiferreira2000 Thanks, confirms that I’m doing it right, but that it’s SuiteCRM which isn’t doing what it’s expected. I would assume that there is bug report for this already, right?

Edit: I see now that you’ve also raised a issue ticket for this yesterday: Google Calendar Sync only works while the access token has not expired Ā· Issue #10674 Ā· salesagility/SuiteCRM Ā· GitHub

Solution for Sessions for Google sync users

Issue 1

The proposed fix suggested on Regression: Local users gets Profile wizard on each login Ā· Issue #10637 Ā· salesagility/SuiteCRM Ā· GitHub still won’t work, because in reloadPreferences() in the modules/UserPreferences/UserPreference.php that inner if

        if ($row) {
            if (!$GLOBALS['current_user']->id || $GLOBALS['current_user']->user_name === $user->user_name){
                $_SESSION[$user->user_name . '_PREFERENCES'][$category] = unserialize(base64_decode($row['contents']));
            }
            $user->user_preferences[$category] = unserialize(base64_decode($row['contents']));
            return true;
        } else {
            if (!$GLOBALS['current_user']->id || $GLOBALS['current_user']->user_name === $user->user_name){
                $_SESSION[$user->user_name . '_PREFERENCES'][$category] = array();
            }
            $user->user_preferences[$category] = array();
        }

will never be true for your Google‐Sync user when run under cron, since current_user is always the admin. As a result, the sync user’s session bucket never gets populated—instead it falls through to the ā€œelseā€ and gets reset to an empty array. That change was introduced in commit ea756b13f0ae606997c0ccb4382e1a72902a9207 (ā€œAdd check for current user on session preferencesā€) and has broken Google‐Sync users

Fix

Or remove the condition completely

if (!$GLOBALS['current_user']->id || $GLOBALS['current_user']->user_name === $user->user_name){

Or add an extra condition like

if (PHP_SAPI === 'cli' || $GLOBALS['current_user']->user_name === $user->user_name){

Whenever PHP is invoked from the command‐line (for example by your cron job), its ā€œServer APIā€ string (PHP_SAPI) is set to cli. In contrast, when PHP runs as an Apache module it might be apache2handler, or under PHP‐FPM it’s fpm‐cgi, etc.

This will always be true in the cron context, because you’re literally calling php -f cron.php (the CLI binary), not going through the web server. That guarantees your session‐reload logic fires under cron even though current_user is still ā€œadmin.

Issue 2

On the other hand, the function getGoogleClient in the file include/GoogleSync/GoogleSyncBase.php needs also a small fix for code optimization so it does not refresh the Google API token every minute. To fix this, pass the ā€œno-sessionā€ flag (0) as the third argument and the category as the fourth:

By changing from

                $this->workingUser->setPreference('GoogleApiToken', base64_encode(json_encode($client->getAccessToken())), 'GoogleSync');

to

$this->workingUser->setPreference('GoogleApiToken', base64_encode(json_encode($client->getAccessToken())), 0,'GoogleSync');

you are making sure that the generated GoogleApiToken is actually saved under the user preference GoogleSync and not Global

If you make this change the token is saved under the GoogleSync category, so subsequent runs will load the existing token and only refresh when it’s actually expired.

The current behaviour means that the function getGoogleClient always perceives the GoogleApiToken as expired and forces the token to be reloaded because it cannot find it in the GoogleSync preference.

@guiferreira2000 can you please make a PR to fix these bugs in core code?

Hello @pgr here’s the pull request

3 Likes