Using Google API to call AppScripts functions with PHP

Get Google API Dependecy scripts

PHP API Client gives an avenue to use Google Services. Download them with composer

composer require google/apiclient:^2.15.0

Set up Front End

Front end should have 2 components

  1. Login Screen
  2. Operations page

Set up Backend

Backend should handle following

  1. Show Login Button.
  2. Get tokens JSON with auth code.
  3. Refresh token on expiry.
  4. Get User Info.
  5. Call API Script.
  6. Revoke Token.

Create a Helper Class

In order to use PHP API Client we need to create a helper class. I create a helper class GoogleAPIHelper with the following methods

public function __construct()
public function initGoogleClient($forJS = false)
public function getRedirectURL()
public function getClient()
public function prepareClientForLogin()
public function getGoogleLoginURL()
public function getTokenFromCode($authCode)
public function saveTokenFromCode($authCode)
public function saveToken($token)
public function getSavedToken()
public function deleteSavedToken()
public function revokeToken()
public function shouldLogin()
public function showUserDetails()
public function isTokenExpired()
public function setAccessToken() 
public function executeAppScriptFunction($function, $parameters = array())
public function getFullURL2Root()
public function get_absolute_path($path, $dirSeperatorOf = 'LINUX') 

Ensure Appropriate URLs.

  1. Hamburger menu(3 lines) >> APIs and Services >> Credentials

  2. Click the Web Client you want to use

  3. Set the following Urls

    1. Authorised JavaScript origins(required only if you use JSAPI)
    2. Authorised redirect URI eg: http://localhost/pjt/tests/googleAPI/PHP/googleLogin.php?action=getTokenFromCode

Authorised redirect URI will be returned with an additional URL parameter code, which will contain authorisation code to retrieve access token. access token is retrieved with googleClient->fetchAccessTokenWithAuthCode($authCode); which retrurn an array like the following

Handling Tokens

Importance of Tokens and how they are obtained is explained here

Tokens received with authcode will be like the following.

 Array
            (
                [access_token] => ya29.a0AfB_byApbns6LcPxBk5rQE51fG9Ie0oOXEwhryVVKyCNO63F2X-uo8cFijqROd6Wrd7V8Sn_9mSNDOf-OMuvfsZ2CGKmzSJCPDkualVTXAPWjOkAt2lvhBzps-mQjYWItRpQS5RP1gnzDWaKJn6iFyZqUEsAFmcu1qEFZwaCgYKAWISARESFQHsvYlsv2tdhmNGHf-vjFmVo_2cpg0173
                [expires_in] => 3599
                [refresh_token] => 1//0gpQCAB4LxfHmCgYIARAAGBASNwF-L9Irp4aWcXP6W-ztwtv79pryRtXx1SUSidneTe4wBgbYBWiRCE13yKsjrN9ggKkeU3qg5f8
                [scope] => https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email openid
                [token_type] => Bearer
                [id_token] => eyJhbGciOiJSUzI1NiIsImtpZCI6ImM3ZTExNDEwNTlhMTliMjE4MjA5YmM1YWY3YTgxYTcyMGUzOWI1MDAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIzNDQ2NDM3Mzk0MzMtcnVyN3JyOTh1NnM1MmY2cTI4ZXRzY2UybzdhamkyOXQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIzNDQ2NDM3Mzk0MzMtcnVyN3JyOTh1NnM1MmY2cTI4ZXRzY2UybzdhamkyOXQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDI5NjQxMzA4MTg3NDg1ODg5MjAiLCJlbWFpbCI6ImxlZ2FsbGl2ZWluZGlhQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiRnZMam9VYlZFSTYwc2ZKWkppX0N1ZyIsIm5hbWUiOiJMZWdhbCBMaXZlIiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hL0FBY0hUdGZybk1INmtEcWlMOVNBSVBhME9WQnlmTjhkT2Y4UnExbHpwR0x5aG55ZT1zOTYtYyIsImdpdmVuX25hbWUiOiJMZWdhbCIsImZhbWlseV9uYW1lIjoiTGl2ZSIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNjkzODQ0NDY3LCJleHAiOjE2OTM4NDgwNjd9.YElnrlOe5XI-BDgTfBh5RBuxh_sA1L4LiKr27021wtchXHRUW7dJ5bcj5OoPNWhYfPEa_8bQoyjb9eGAonXEltv-behYUpFEAzvNmF64Mxk1B9oGHeek30KFkMjBzG3Um5nHS_kzLDqdQlrQZvbsob29a6jJb1O6Bl6iqM48FALWKgOoSHmDCiK5cV2B18ao_bO-fzzEyBVUUF_m-NUcWlvZo4K51mU7es6hI7ii2btqNdjzGmu8a38pkYHSJBTPxFpsn5xneLG2wJ9R4Z7qs-K49fO33_bvaoNjIUKtmHGAw0_e_rC8yZIDkhjHnOcJS4Ll-Jz-AmPR60hObXkXYw
                [created] => 1693844470
            )

PHP Code to call API Scripts

    public function executeAppScriptFunction($function, $parameters = array())
    /* based on 
        1. https://web.archive.org/web/20190831093843/http://ctrlq.org/google.apps.script/docs/guides/rest/quickstart/php.html
        2. https://developers.google.com/apps-script/api/reference/rest/v1/scripts/run#path-parameters
    */
    {
        $this->setAccessToken();
        $service = new Google_Service_Script($this->googleClient);

        $scriptId = '1E4CbbxJjoeunV9lYnmr5zLCZkcXudOJb8hcdOgq4jQfVrg7v6tD1TAsK';
        $scriptId = 'AKfycbwZywEHapiFLKKvDUstnYXbEXwYVVXz0T7hDzcod_8_c-JGH7L97ljOEveLvrlOKBErDw';/*For API Executable scripts, this is deployment_Id in Manage Deployments*/

        // Create an execution request object.
        $request = new Google_Service_Script_ExecutionRequest();

        $request->setFunction($function);
        if(count($parameters) > 0)
        {
            $request->setParameters($parameters);
        }//if(count($parameters) > 0)

        try {
          // Make the API request.
          $response = $service->scripts->run($scriptId, $request);

          if ($response->getError()) {
            // The API executed, but the script returned an error.

            // Extract the first (and only) set of error details. The values of this
            // object are the script's 'errorMessage' and 'errorType', and an array of
            // stack trace elements.
            $error = $response->getError()['details'][0];
            printf("Script error message: %s\n", $error['errorMessage']);

            if (array_key_exists('scriptStackTraceElements', $error)) {
              // There may not be a stacktrace if the script didn't start executing.
              print "Script error stacktrace:\n";
              foreach($error['scriptStackTraceElements'] as $trace) {
                printf("\t%s: %d\n", $trace['function'], $trace['lineNumber']);
              }
            }
          } else {
            // The structure of the result will depend upon what the Apps Script
            // function returns. Here, the function returns an Apps Script Object
            // with String keys and values, and so the result is treated as a
            // PHP array (folderSet).
            $resp = $response->getResponse();
            $folderSet = $resp['result'];
            if (!is_array($folderSet) || count($folderSet) == 0) {
              print "No folders returned!\n";
              print "Result returned is {$folderSet} \n";
            } else {
              print "Folders under your root folder:\n";
              foreach($folderSet as $id => $folder) {
                printf("\t%s (%s)\n", $folder, $id);
              }
            }
          }
        } catch (Exception $e) {
          // The API encountered a problem before the script started executing.
          echo 'Caught exception: ', $e->getMessage(), "\n";
        }       
    }//public function executeAppScriptFunction()

Using Helper Class

<?php
/*

This PHP script URL : http://localhost/pjt/tests/googleAPI/PHP/googleLogin.php
URL for getting Auth Code : http://localhost/pjt/tests/googleAPI/PHP/googleLogin.php?action=getTokenFromCode

https://github.com/googleapis/google-api-php-client

Test
1. login
    - initiate client
    - redirect to login
    - get code
    - get access token and refresh token using code
2. access token
3. refersh token
4. execute api function 
5. revoke
6. refresh access token in js app with authorisation code popup model https://developers.google.com/identity/oauth2/web/guides/use-code-model#popup-mode

get code from authorisation code model https://developers.google.com/identity/oauth2/web/guides/use-code-model#popup-mode
save refresh token and send access token to js app

composer require google/apiclient:^2.15.0

*/
ob_start();
require_once __DIR__.'/private/Helpers/GoogleAPILoginHelper.php';
define("OSOL_PROJECT_ROOT_PATH",__DIR__);

$googleLoginHelper = new GoogleAPIHelper();

if (isset($_GET['action']))
{

    $forJS =  ($_GET['action'] == "getTokenFromCode4JS");
    $googleLoginHelper->initGoogleClient($forJS);    
    switch($_GET['action'])
    {
        case "getTokenFromCode4JS"://googleLogin.php?action=getTokenFromCode4JS&code=
                //die("_REQUEST IS " . print_r($_REQUEST,true));
                //die("REQUEST_URI IS " . print_r($_SERVER['REQUEST_URI'],true));
                //die("_REQUEST code IS " . print_r($_GET['code'],true));
            if (isset($_REQUEST['code']) && $_REQUEST['code'] != '') {
                $token = $googleLoginHelper->getTokenFromCode($_REQUEST['code']);
                $googleLoginHelper->saveToken($token);
                die(json_encode($token));// . " RedirectURI is ". $googleLoginHelper->getRedirectURL());//."Code sent is " . $_POST['code']);
            }
            else
            {
                die("Invalid CODE sent");
            }
            break;  
        case "getNewAccessTokenFromServer4JS": //googleLogin.php?action=getTokenFromCode4JS
            $googleLoginHelper->setAccessToken();
            $refreshedToken =  $googleLoginHelper->getSavedToken();
            //echo "<pre>".print_r($refreshedToken,true)."</pre>";
            $newAccessToken = $refreshedToken['access_token'];
            //die(json_encode($refreshedToken));
            die($newAccessToken);
            break;

        case "getTokenFromCode"://googleLogin.php?action=getTokenFromCode&code=   
            if (isset($_REQUEST['code'])) {
                $token = $googleLoginHelper->saveTokenFromCode($_REQUEST['code']);
                header("location: ". $_SERVER['PHP_SELF']);
            }
            break;
        case "deleteSavedToken":
            $googleLoginHelper->deleteSavedToken();
            echo "Saved Token Deleted successfully";
            break;
        case "revokeToken":
            $googleLoginHelper->revokeToken();
            echo "Token revoked and deleted successfully";
            break;
        case "executeAPIFunction":
            if(isset($_GET['setFunction']))
            {
                $function = $_GET['setFunction'];
                $parameters =  array();
                switch($_GET['setFunction'])
                {
                    case 'add':
                        $parameters =  array(8,9);                  
                        break;  
                    default:    
                        $function =  "getFoldersUnderRoot";
                }//switch($_GET['action'])
                echo "function called is " . $_GET['setFunction']."<br />";
            }//if(isset($_GET['setFunction']))
            $googleLoginHelper->executeAppScriptFunction($function, $parameters);
            break;
    }//switch($_GET['action'])

    exit;
}
else
{
    $googleLoginHelper->initGoogleClient();
}//if (isset($_GET['action']))

require_once(__DIR__."/private/frontend/frontend.php");
?>

frontend.php


<div class="container">
  <h1>My First Material Design Page</h1>
  <p>This is some text.</p>

  <?php require_once(__DIR__.'/bodyContent/index.php');?>

</div>

bodyContent/index.php

<?php

//if(!isset($_GET['action']) || $_GET['action'] == "")
if($googleLoginHelper->shouldLogin())//file_exists($this->tokenSavedFile);
{
    echo "redirectURI is " . $googleLoginHelper->getRedirectURL()."<br />\r\n";
    $googleLoginHelper->prepareClientForLogin();
    echo $googleLoginHelper->getGoogleLoginURL();
}
else //if($_GET['action'] == "getTokenFromCode")
{
    $googleLoginHelper->showUserDetails()
    //$googleLoginHelper->setAccessToken();
    // show bottons
    // show add button
    ?>
    <a href="?action=executeAPIFunction&setFunction=add" >Add</a><br />
    <a href="?action=executeAPIFunction&setFunction=getFoldersUnderRoot" >getFoldersUnderRoot</a><br />
    <a href="?action=deleteSavedToken" >Delete Saved Token</a><br />
    <a href="?action=revokeToken" >Revoke Token</a><br />
    <?php
    // show folder Info button
    // revoke button
}//if($_GET['action'] == "getTokenFromCode")
  ?>