How to update Google Spreadsheet cell using PHP API - google-drive-api

I would like to know how I can interact with a Google spreadsheet using PHP.
I've looked through many pages of Google's documentation, however, none of that is doing what I'm looking for.
My goal is to be able to change the content of the cells using oAuth (not the email/pass).
Please, forgive me if this is a RTFM issues, but I did spend more than 2 weeks with this with no result. :/

You can do this with asimlqt/php-google-spreadsheet-client library.
Install the library via composer:
"require": {
"asimlqt/php-google-spreadsheet-client": "dev-master"
}
Bootstrap composer in your PHP file:
require('vendor/autoload.php');
Follow the steps to get a Google API client ID, client email, and P12 access key, as explained here:
https://github.com/asimlqt/php-google-spreadsheet-client/wiki/How-to-use-%22Service-account%22-authorization-(rather-than-user-based-access-refresh-tokens)
Use the following code:
$accessToken = getGoogleTokenFromKeyFile(CLIENT_ID_HERE, CLIENT_EMAIL_HERE, P12_FILE_PATH_HERE);
use Google\Spreadsheet\DefaultServiceRequest;
use Google\Spreadsheet\ServiceRequestFactory;
ServiceRequestFactory::setInstance(new DefaultServiceRequest($accessToken));
// Load spreadsheet and worksheet
$worksheet = (new Google\Spreadsheet\SpreadsheetService())
->getSpreadsheets()
->getByTitle('xxxxxxxxx') // Spreadsheet name
->getWorksheets()
->getByTitle('xxxxxxxxx'); // Worksheet name
$listFeed = $worksheet->getListFeed();
// Uncomment this to find out what Google calls your column names
// print_r($listFeed->getEntries()[0]->getValues());
// Add a new blank row to the spreadsheet, using the column headings
$listFeed->insert(['name' => 'Simon', 'age' => 25, 'gender' => 'male']);
/**
* Retrieves a Google API access token by using a P12 key file,
* client ID and email address
*
* These three things may be obtained from
* https://console.developers.google.com/
* by creating a new "Service account"
*/
function getGoogleTokenFromKeyFile($clientId, $clientEmail, $pathToP12File) {
$client = new Google_Client();
$client->setClientId($clientId);
$cred = new Google_Auth_AssertionCredentials(
$clientEmail,
array('https://spreadsheets.google.com/feeds'),
file_get_contents($pathToP12File)
);
$client->setAssertionCredentials($cred);
if ($client->getAuth()->isAccessTokenExpired()) {
$client->getAuth()->refreshTokenWithAssertion($cred);
}
$service_token = json_decode($client->getAccessToken());
return $service_token->access_token;
}

Drive API doesn't provide any means to edit a spreadsheet, Spreadsheets API contains low level cell modification methods. As a note, you can't use Google APIs PHP Client Library to consume the Spreadsheets API.

API v4, you can use curl. Get Access Token then use curl. You can update single row or multiple rows Or you can update single column or multiple columns. If you are updating Single Column or Single row then use https://sheets.googleapis.com/v4/spreadsheets/sheetID/values/RowORColumnRange/?valueInputOption=RAW&includeValuesInResponse=true
sheetID = FileID
RowORColumnRange = Sheet!A1 or Sheet!B2
Pass value data with PUT method like
$update['values'] = [[1234]];
for the Batch Update instead of use method values in the URL you can change to values:batchUpdate. and then pass other data in array and use method POST;
You can ref this https://developers.google.com/sheets/api/guides/migration

This is the solutions i got for my issue :
Zend GData Library
Zend Gdata for Spreadsheet with the examples code

Related

FormResponse.GetId() returning incorrect response ID?

I am trying to set up a webhook for my Google form and all was well until I tried retrieving the ID of the response to use in a URL like so:
function onSubmit(e) {
var form = FormApp.getActiveForm();
var allResponses = form.getResponses();
var latestResponse = allResponses[allResponses.length - 1];
var formID = "top-secret";
var link = "https://docs.google.com/forms/d/" + formID + "/edit#response=" + latestResponse.getId();
}
I have checked the Google Apps Script documentation and it looks like getId() is what I am after here. The issue with this is that the ID I get from this is incorrect. When I try to visit the link with this ID, I get Response not found.
getId() returns 2_ABaOnufCTeHiCWUFr0VRcCb8wkiWIZzeIZQU0xyP5pppD7oSr3nPFQ4y8VdOA96dYU_5Ros for example whereas the actual response ID from the form itself returns ACYDBNiLhfYgdxvkakafdps42HYuaZYkU-LrsjBtvt9ABhzal71zZWZnNsTxHVTrm6gom_E.
Do I need to do some conversion to make this work or is there some other function I should be using instead?
I thought that in the current stage, unfortunately, the value of responseId of https://docs.google.com/forms/d/{formId}/edit#response={responseId} cannot be retrieved by Google Forms service. Rer So in this case, it is required to use several workarounds.
Workaround 1:
It uses getEditResponseUrl(). When this method is used, the following URL is obtained.
https://docs.google.com/forms/d/e/{ID1}/viewform?edit2={ID2}
In this case, ID1 is not the form ID. ID2 is the same with the ID retrieved by latestResponse.getId() in your script.
When your script is used, you can retrieve this URL by latestResponse.getEditResponseUrl().
Workaround 2:
It uses toPrefilledUrl(). When this method is used, the following URL is obtained.
https://docs.google.com/forms/d/e/{ID1}/viewform?usp=pp_url&entry.###=###&entry.###=###,,,
In this case, ID1 is not the form ID. And, each value is given by entry.###=###.
When your script is used, you can retrieve this URL by latestResponse.toPrefilledUrl().
Updated at March 16, 2022:
Workaround 3:
In this workaround, Google Forms API is used. Google Forms API has published a stable as the generally-available v1. Ref
In the current stage, the endpoint changed from https://forms.googleapis.com/v1beta/forms/ to https://forms.googleapis.com/v1/forms/. And, you can also use Forms API without joining to "Early Adopter Program".
But, as an important point, in order to use Forms API, it is required to link Google Cloud Platform Project to Google Apps Script Project. In the current stage, Google Forms API cannot be used with Advanced Google services. So please link Google Cloud Platform Project to Google Apps Script Project. Ref And also, please enable Google Forms API at API console. Ref
When Google Forms API is used, your goal can be directly achieved. In this case, "Method: forms.responses.list" is used.
function myFunction() {
const formId = "###"; // Please set your Form ID.
const url = `https://forms.googleapis.com/v1/forms/${formId}/responses?fields=*&pageSize=5000`;
const res = UrlFetchApp.fetch(url, { headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() } });
const obj = JSON.parse(res.getContentText());
const sorted = obj.responses.sort((a, b) => new Date(a.lastSubmittedTime).getTime() > new Date(b.lastSubmittedTime).getTime() ? -1 : 1);
const responseId = sorted[0].responseId; // This is the value you expect.
const link = `https://docs.google.com/forms/d/${formId}/edit#response=${responseId}`;
console.log(link)
}
In this case, formId is the form ID. And, the retrieved value of responseId is the response ID of the last response.
In this case, as a sample, pageSize=5000 is used. When more responses are existing, please use pageToken.
The scope of https://www.googleapis.com/auth/forms.responses.readonly is used in this method.
References:
getEditResponseUrl()
toPrefilledUrl()
Method: forms.responses.list
Create surveys, quizzes, and more using the Google Forms API, now generally available

How to get the correct XPath for Google Sheets formula ImportXML

I tried to set up importXML in my Google Sheets, I used the method to copy full Xpath.
It seems not working at all.
After reading Xpath still not sure how to get the right path just for the token price.
Hope can get some idea or document to read to get the value I need.
Thanks a lot for reading this.
Wish you to have a nice day.
=IMPORTXML("https://info.osmosis.zone/token/DSM","/html/body/div[1]/div/div[3]/div/div/p")
Create a custom function by:
Opening Script Editor (Tools > Script Editor or Extensions > Apps Scripts)
And then enter the following within the script:
/**
* #return Specific value out of the value of different fields
* #customfunction
*/
function PARSEVALUE(Url,itemKey) {
var res = UrlFetchApp.fetch(Url);
var content = res.getContentText();
var jsonObject = JSON.parse(content);
return jsonObject[0][itemKey];
}
In your spreadsheet, use the function like:
=PARSEVALUE("https://api-osmosis.imperator.co/tokens/v1/DSM","price")
There are different values for different keys, as in price,symbol,name,liquidity,volume_24h e.t.c. you can grab using this function.
The page contents
You need to enable JavaScript to run this app.
JavaScript content is not supported by any IMPORT formulae. the best course of action would be to find a different source for your scrapping.
You need to use a specific url (api) which contains the json.
edit :
According to the url provided by #QHarr and if you want to retrieve all informations from url, try
function parseValues(url) {
const jsn = JSON.parse(UrlFetchApp.fetch(url).getContentText())
const headers = Object.keys(jsn[0]);
return ([headers, ...jsn.map(obj => headers.map(header => Array.isArray(obj[header]) ? obj[header].join(",") : obj[header]))]);
}
and in your sheet
=parseValues("https://api-osmosis.imperator.co/tokens/v1/DSM")

Google Apps Script - Contacts - Query on multiple strings

I'm working on a script to remove and re-add about 100 contacts. I have 12 different search criteria for ContactsApp.getContactsByEmailAddress, which the initiated know takes 30+ seconds to run. Is there some way I can only run it once and search all of my criteria? I've looked for others trying to do this same thing and was unsuccessful.
Below is one of the searches from my function (repeats 12 times with various search terms being passed to ContactsApp.getContactsByEmailAddress). I added the try-catch block because the script kept throwing errors out for seemingly no reason during various delete loops.
Would appreciate any and all advice.
var apa = ContactsApp.getContactsByEmailAddress('apa.')
try{
for (var i in apa) {
apa[i].deleteContact()
}
} catch(e){
Logger.log(e)
}
With the Contacts Service, you are limited to a single search criteria. Thus, the only way to search multiple patterns is to call the method once with each search parameter. You can, thankfully, use standard programming practices to minimize the amount of repeated code:
function getContactsWithEmails(emailSearchCriteria) {
if (!emailSearchCriteria || !emailSearchCriteria.length)
throw new Error("No search inputs given");
// Collect the results of each search into a single Array.
const matches = [];
emailSearchCriteria.forEach(function (email) {
var results = ContactsApp.getContactsByEmailAddress(email);
if (results.length)
Array.prototype.push.apply(matches, results);
else
console.log({message: "No results for search query '" + email + "'", query: email, resultsSoFar: matches});
});
return matches;
}
function deleteContacts(arrayOfContacts) {
if (!arrayOfContacts || !arrayOfContacts.length)
throw new Error("No contacts to delete");
arrayOfContacts.forEach(function (contact) {
ContactsApp.deleteContact(contact);
});
}
// Our function that uses the above helper methods to do what we want.
function doSomething() {
// Define all email searches to be performed.
const emailFragmentsToSearchWith = [
"email1",
...
"emailN"
];
const matchingContacts = getContactsWithEmails(emailFragmentsToSearchWith);
if (matchingContacts.length) {
/** do something with the contacts that matched the search.
* someMethodThatSavesContacts(matchingContacts);
* someMethodThatModifiesContacts(matchingContacts);
* deleteContacts(matchingContacts);
* ...
*/
}
/** do other stuff that doesn't need those contacts. */
}
The Google Calendar v3 GData API, as mentioned in this SO question, does support multiple query parameters. However, there is no simple integration with this API - you will need to write the appropriate URL requests and execute them with UrlFetchApp.
In addition to the Google Contacts API, you could use the Google People REST API, specifically the people.connections#list endpoint.
Both of these APIs require you to associate your Apps Script project with a Google Cloud Project that has the respective API enabled, and will likely require you to manually set the scopes your Apps Script project requires in its manifest file, in addition to providing OAuth2 authorizations of the associated HTTP requests you make to the API endpoints.
References
Accessing External APIs
Enabling Google APIs (steps 3-5)
Google Contacts API v3
Google People API
Array#forEach

Google Apps Script: Export all Google contacts as CSV

I have just started to learn Google Apps Script to solve one practical task. I'd like to write a script that periodically exports all contacts in my Google account into a CSV file and sends it to a predefined email address. This should be a kind of automatic versioned backup if data loss will occur in my contact list.
Actually what I am trying to do is to use the native export functionality available in the web interface of the Google contacts (More > Export) in my script. I skimmed through the Google API, but I could not find a service or object in the Google API that does what I need. Is it possible at all?
Here's something I threw together to get a few of the basic fields in your contacts into something that could be a csv output. You may want to add more fields and perhaps use different delimiters.
function getAllContacts() {
var contactsA=ContactsApp.getContacts();
var s='';
var br='<br />';//line delimiter change to linefeed when not using html dialog
var dlm=' ~~~ ';//field delimiter
for(var i=0;i<contactsA.length;i++)
{
s+=Utilities.formatString('<br />\'%s\',\'%s\',\'%s\',\'%s\',\'%s\'%s',
(typeof(contactsA[i].getFullName())!='undefined')?contactsA[i].getFullName():'',
(typeof(contactsA[i].getAddresses().map(function (v,i,A) { return A[i].getAddress();}))!='undefined')?contactsA[i].getAddresses().map(function (v,i,A) { return A[i].getAddress();}).join(dlm):'',
(typeof(contactsA[i].getEmails().map(function(v,i,A) {return A[i].getAddress();}))!='undefined')?contactsA[i].getEmails().map(function(cV,i,A) {return A[i].getAddress();}).join(dlm):'',
(typeof(contactsA[i].getPhones().map(function(v,i,A){return A[i].getPhoneNumber();}))!='undefined')?contactsA[i].getPhones().map(function(v,i,A){return A[i].getPhoneNumber();}).join(dlm):'',
(typeof(contactsA[i].getCompanies().map(function(v,i,A){return A[i].getCompanyName();}))!='undefined')?contactsA[i].getCompanies().map(function(v,i,A){return A[i].getCompanyName();}).join(dlm):'',br);
}
var ui=HtmlService.createHtmlOutput(s).setWidth(800) ;
SpreadsheetApp.getUi().showModelessDialog(ui, 'Contacts')
}

Create new file in a folder with Apps Script using Google Advanced Drive service

There are four ways to create a new file:
DocsList - Shown as DocsList in the Main List. Built in to Apps Script.
DriveApp - Shown as Drive in the Main List. Built in to Apps Script.
Drive API - Also shown as Drive in the Main List. Must be added to Apps Script.
DocumentApp - Shown as Document in the Main List. Built in, but only creates a document file.
They are all called services. Drive API is called an advanced service. So, which one should you use? I don't know, it depends. This question is about the Drive API Advanced Service.
I don't want to use 2 or 3 of the services to get the job done. I'd like to just use one of them. But to decide which one to use, I need to know the capabilities and options of all of them. If the simplest and easiest one to use will do everything I want, then I'll use that.
If I can create a new file with Drive API, but then I need to use the DriveApp service to move the file I created with Drive API, to the folder I want it in, then using Drive API in that particular situation is pointless.
I can create a new file in my Google Drive from a Google Apps Script .gs code, but the file gets written to the main 'My Drive'. I want to write the file directly to a sub-folder. My current code is:
var fileNameSetA = 'someFile.jpg';
var uploadedBlobA = an image uploaded with a file picker;
var fileTestDrive = {
title: fileNameSetA,
mimeType: 'image/jpeg'
};
fileTestDrive = Drive.Files.insert(fileTestDrive, uploadedBlobA);
Even though the code works, I have no idea why the syntax is the way it is, and I can't find documentation that tells me why. I can find a list of properties:
The title: and mimeType: are Optional Properties as part of the Request Body. From the example, the Optional Properties are obviously put in a key:value paired object. So, is the syntax:
Drive.Files.insert(optional properties, content);
There are also Required query parameters of:
uploadType --> media, multipart, resumable
But I don't see any required uploadType parameter designated anywhere in the example code. So, I don't understand Google's documentation.
Google Documentation Insert
Is it possible to write directly to a specific drive with Google Advanced Drive service in a Apps Script .gs code file? How do I do it?
The easiest way to create a new file is to use DriveApp which comes with pure Google Apps Script:
var dir = DriveApp.getFolderById("{dir_id}");
var file = dir.createFile(name, content);
If you do not know exact directory's id you can get the folder by its name:
var dir = DriveApp.getFoldersByName(name).next();
The next() is there because getFoldersByName() returns collection of all directories whose names match given value.
Also check DriveApp docs: https://developers.google.com/apps-script/reference/drive/drive-app
Maybe this is a bit late, but by looking at the REST API docs, it shows that you can use Drive.Files.insert to insert into any folder. You simply have to add the folder's ID in the properties of the file you are inserting as such:
var file = {
title: 'myFile',
"parents": [{'id':folder.getId()}], //<--By setting this parent ID to the folder's ID, it creates this file in the correct folder.
mimeType: 'image/png'
};
Folder ID can be obtained from the shareable link using the Google Drive GUI or as shown here. (e.g. Use the Execute function on the right.)
Alternatively, you can access the folder by name by replacing the folder.getID() with Drive.getFoldersByName('name of folder').
This is helpful because Drive.Files.insert() accepts arguments while Drive.createFile() and Drive.createFolder() do not.
The documentation for INSERT for the Drive API is found at this link:
Drive API for INSERT
There is a section for Request body. One of the Optional Properties for Insert is parents[]. The brackets [] indicate that a list of parents can be designated. The documentation for parents[] states this:
Collection of parent folders which contain this file. Setting this
field will put the file in all of the provided folders. On insert, if
no folders are provided, the file will be placed in the default root
folder.
So, . . . using Insert in Drive API, . . . . CAN write a new file directly to a subfolder. It's possible.
Now, the nomenclature and syntax for the Google Drive SDK, HTTP request is different than what is inside of Apps Script.
The syntax for invoking the Drive API HTTP Request inside of a .gs file is one of the following three:
Drive.Files.insert(FILE resource)
Drive.Files.insert(FILE resource, BLOB mediaData)
Drive.Files.insert(FILE resource, BLOB mediaData, OBJECT optionalArgs)
The syntax shown in the list above is from the auto-complete drop down list inside the Apps Script code editor. If you type Drive.Files. a list of possible methods will appear. I can't find information about the syntax anywhere in the online documentation.
So, where does the parents[] optional property go? Well, it's not a Blob, so we can rule that out. It's either FILE resource, or OBJECT optionalArgs. optionalArgs indicates that it's an object, but FILE resource is actually also an object.
In the examples, the FILE resource is constructed as key:value pair object.
Uploading Files - Advanced Drive Service - Google Documentation
Direct Answer to Question
This summary from https://developers.google.com/apps-script/advanced/drive sums things up pretty well:
The advanced Drive service allows you to use the Google Drive web API
in Apps Script. Much like Apps Script's built-in Drive service, this
API allows scripts to create, find, and modify files and folders in
Google Drive. In most cases, the built-in service is easier to
use, but this advanced service provides a few extra features,
including access to custom file properties as well as revisions for
files and folders.
Like all advanced services in Apps Script, the advanced Drive
service uses the same objects, methods, and parameters as the public
API.
Essentially DriveApp is easier to use than Drive, but Drive gives you more functionality since it shares the same functionality of the public API. I was not able to see how to save a file to a Shared/Team drive using DriveApp, so I ended up using Drive. The pain came around lack of documentation for the Google Apps Script implementation of Drive.
Explanation of My Solution and Code Sample:
A specific implementation of saving a file to Google drive, but this will likely be useful for others. It took me a whole day to figure this out since the documentation and code examples for Google Apps scripts is severely lacking. My use case was for saving a JSON file to a shared Google Drive (Team Drive).
There are three parameters that I did not have at first and my files were not uploading. I am not sure if all are necessary. One was the "kind": "drive#parentReference" part of the parents metadata. The next was "teamDriveId": teamDriveId which is also in the metadata. The last parameter was "supportsAllDrives": true which I passed in the optional parameter location of Drive.Files.insert().
I found the API explorer on https://developers.google.com/drive/api/v2/reference/files/insert to be very useful in figuring out which parameters were needed and how they needed to be formatted. I basically edited values in the explorer till I got a network request that worked. I then pulled the parameters I used into my Google Apps script.
/**
* Creates a JSON file in the designated Google Drive location
* #param {String} jsonString - A JS string from the result of a JSON.stringify(jsObject)
* #param {String} filename - The filename. Be sure to include the .json extension
* #param {String} folderId - The ID of the Google Drive folder where the file will be created
* #param {String} teamDriveId - The ID of the team drive
* #return {void}
*/
function createJSONFileInDriveFolder(jsonString, filename, folderId, teamDriveId) {
var metadata = {
"title": filename,
"mimeType": "application/json",
"parents": [
{
"id": folderId,
"kind": "drive#parentReference"
}
],
"teamDriveId": teamDriveId
};
var optionalParams = {
"supportsAllDrives": true
};
try {
var jsonBlob = Utilities.newBlob(jsonString, 'application/vnd.google-apps.script+json');
Drive.Files.insert(metadata, jsonBlob, optionalParams);
} catch (error) {
Logger.log(error);
}
}
var searchthreads = GmailApp.search('in:inbox AND after:2020/11/30 AND has:attachment');//"in:all -in:trash category:social older_than:15d
Logger.log("GMAIL thread 0:"+ searchthreads[0].getId());
Logger.log("GMAIL thread 1:"+ searchthreads[1].getId());
Logger.log("GMAIL thread 2:"+ searchthreads[2].getId());
Logger.log("Active User: " + me);
Logger.log("Search Thread: " + searchthreads.length);
Logger.log("Gmail lenght" + gmailthread.length);
//Logger.log("Gmail lenght" + gmailMessages.length);
for (var i in searchthreads){
var messageCOunt = searchthreads[i].getMessageCount();
Logger.log("messageCOunt :" + messageCOunt);
var messages = searchthreads[i].getMessages();
for (var m in messages){
var messagesender = messages[m].getFrom();
var messageDate = messages[m].getDate();
var messageReplyTo = messages[m].getReplyTo();
var messagesubject = messages[m].getSubject();
var messagebody = messages[m].getSubject();
var messagephoneNo = messages[m].getSubject();
//messages[m].isInInbox();
var messageid = messages[m].getId();
var messageplainbody = messages[m].getSubject();//messages[0].getPlainBody();
var EmailStatus ='N';
var ApptStatus = "CVReceived";// Tracking till candidate offer and payout
var messageattachement = messages[m].getAttachments();
//var png=UrlFetchApp.fetch(messageattachement).getBlob();
//https://drive.google.com/drive/folders/1RY4i6FwUvfy5OxrJ1pZTxJAOxjFFXbhz?usp=sharing
var folder = DriveApp.getFolderById("1RY4i6FwUvfy5OxrJ1pZTxJAOxjFFXbhz");
// DriveApp.getFolderById("1RY4i6FwUvfy5OxrJ1pZTxJAOxjFFXbhz").createFile(png);
//DriveApp.createFile();
for (var k in messageattachement){
var filename = messageattachement[k].getName();
var filesize = messageattachement[k].getSize();
var filecontent = messageattachement[k].getContentType();
var fileBlob = messageattachement[k].getAs(filecontent);
var filecpblob = messageattachement[k].copyBlob();
//folder.createFile(filename, messageattachement);
var file = {
title: filename,
"parents": [{'id':folder.getId()}],
mimeType: filecontent
};
file = Drive.Files.insert(file, filecpblob);
//DataStudioApp
Logger.log('ID: %s, File size (bytes): %s', file.id, file.fileSize);
//folder.createFile(filecpblob);
}
var processeddate = new Date();
I know it's been a while since this question was posted. But here is the solution to help other readers. When using Drive.Files.insert() method, in order to specify a location for the inserted file, you must specify the parents[] property within the FILE resource. So expanding on #Alan Wells response here is the syntax for writing a blob as Goggle Spreadsheet format in a specific folder.
let newFile = {title: 'Title goes here', parents: [{id: targetFolderId}]};
let savedFile = Drive.Files.insert(
newFile,
blobGoesHere,
{mimeType: MimeType.GOOGLE_SHEETS, convert: true});
Please note that parents: takes an array of objects. You can specify multiple locations for a single file (it will be created in all the specified folders). Even if you want to use a single location you still have to provide this object in a list.
I was able to use the DriveApp to create a file in a specified folder this way.
var driveFolder = DriveApp.getFolderByName("MyDriveFolder");
var file = driveFolder.createFile(formObject.txtReceipt);
file.setName("MyFile");
PS: formObject.txtReceipt is coming from a file upload control on a form in the html and this returns a blob