Document is Inaccessible Error After Setting Sharing Permissions in Google Apps Script

257 views
Skip to first unread message

Meir Horwitz

unread,
Nov 19, 2024, 7:57:36 AM11/19/24
to Google Apps Script Community
I'm facing an issue in my Google Apps Script where I copy a Google Doc template and set its sharing permissions to "Anyone with a link can edit." Despite setting the permissions, I'm encountering the following error:
Error in createPdfWithPlaceholders: The document is inaccessible. Please try again later.

It worked for me in the previous version of the code when it handled only one template but in the current version, when I added another template and adjusted the code, I got the error I mentioned above.

I am attaching a link to a copy of my project:

https://docs.google.com/spreadsheets/d/1GfyWEVgu4GkmMrP6SC2WPPkeeRy_S6WSj1V7Aibt1-U/edit?gid=26044958#gid=26044958

Basically, it is a signing documents system that takes placeholder values from a Google form (Form Responses 1) and makes a copy of the template, and plants the PH values in the document's PH. The script reads the values according to the settings tab. 
A short description of the functionality:
  • Form Submission: Users fill out a Google Form, selecting one or both documents to sign (e.g., Enrollment Contract, Permission for Trips).
  • Template Selection: Based on form responses, the system dynamically selects templates stored as Google Docs.
  • PDF Generation: Placeholders in the templates are replaced with form data (e.g., name, email), and PDFs are created.
  • Signing Process: Users receive a signing link via email, allowing them to preview and sign documents using a web-based signature pad.
  • Storage & Updates: Signed documents are saved to Google Drive, and the Google Sheet linked to the form is updated with the signing status, date, and document link.
  • Emailing Signed Copies: A copy of the signed document is emailed to the user.
    All the placeholder keys, templates and folder ings are in the settings tab.
Below is the old version that used to work well (one template):

// Utility function to get settings from the 'Settings' tab

function getSettings_() {

 const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Settings');

 if (!sheet) {

   throw new Error('Settings sheet not found.');

 }


 const settings = {};

 const dataRange = sheet.getRange(1, 1, sheet.getLastRow(), 2); // Read keys and values from the first two columns

 const data = dataRange.getValues();


 data.forEach(([key, value]) => {

   if (key) {

     settings[key.trim()] = value ? value.toString().trim() : '';

   }

 });


 Logger.log('Settings Object:', settings); // Log settings for debugging

 return settings;

}


// Extract the actual file ID from a Google Doc link or a folder link

function getActualFileId(link) {

 const docRegex = /\/d\/([a-zA-Z0-9-_]+)/;

 const folderRegex = /\/folders\/([a-zA-Z0-9-_]+)/;

 const docMatch = link.match(docRegex);

 const folderMatch = link.match(folderRegex);


 if (docMatch) {

   Logger.log(`Extracted File ID: ${docMatch[1]}`);

   return docMatch[1];

 } else if (folderMatch) {

   Logger.log(`Extracted Folder ID: ${folderMatch[1]}`);

   return folderMatch[1];

 }


 throw new Error(`Invalid Google Doc or folder link: ${link}`);

}


// Replace placeholders and create a PDF

function createPdfWithPlaceholders(templateLink, valuesMap) {

 try {

   const templateId = getActualFileId(templateLink);

   Logger.log(`Using Template ID: ${templateId}`);

   const template = DriveApp.getFileById(templateId);

   const fileName = `Document for Signature`;

   const copy = template.makeCopy(fileName);

   const doc = DocumentApp.openById(copy.getId());


   // Replace placeholders in the document

   replacePlaceholders(doc, valuesMap);


   // Save changes to the document

   doc.saveAndClose();

   Logger.log(`Placeholders replaced successfully in document ID: ${copy.getId()}`);


   return { docId: copy.getId() };

 } catch (error) {

   Logger.log(`Error in createPdfWithPlaceholders: ${error.message}`);

   throw error;

 }

}


// Replace placeholders in the document

function replacePlaceholders(doc, valuesMap) {

 const body = doc.getBody();

 Object.entries(valuesMap).forEach(([placeholder, value]) => {

   const searchPlaceholder = `{{${placeholder}}}`;

   body.replaceText(searchPlaceholder, value);

   Logger.log(`Replaced placeholder {{${placeholder}}} with value: ${value}`);

 });

}


// Save signature to the PDF and send to signer

function saveSignatureWithPdf(docId, dataURL, row) {

 try {

   // Get settings and validate folder

   const settings = getSettings_();

   const folderId = getActualFileId(settings.signedFolderLink);

   const folder = DriveApp.getFolderById(folderId);

   Logger.log(`Saving signed PDF to folder ID: ${folderId}`);


   // Decode the signature data URL into a blob

   const signatureBlob = Utilities.newBlob(

     Utilities.base64Decode(dataURL.split(',')[1]),

     'image/png',

     'signature.png'

   );

   Logger.log('Signature blob created successfully.');


   // Open the document and append the signature

   const doc = DocumentApp.openById(docId);

   const body = doc.getBody();


   if (!body) {

     throw new Error('Unable to access the document body to append the signature.');

   }


   const paragraph = body.appendParagraph('\nSigned by:');

   const img = paragraph.appendInlineImage(signatureBlob);

   img.setWidth(200).setHeight(100);

   Logger.log('Signature appended to the document.');


   // Save changes to the document

   doc.saveAndClose();


   // Convert the signed document to a PDF

   const updatedDoc = DriveApp.getFileById(docId);

   if (!updatedDoc) {

     throw new Error(`Unable to access the document with ID: ${docId}`);

   }


   const pdfBlob = updatedDoc.getAs('application/pdf');

   const pdfFile = folder.createFile(pdfBlob.setName(`Signed Document.pdf`));

   Logger.log(`Signed PDF created: ${pdfFile.getName()} (${pdfFile.getId()})`);


   // Update the Form Responses sheet

   const signedDocLink = pdfFile.getUrl();

   const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 1');

   if (!sheet) {

     throw new Error('Form Responses sheet not found.');

   }


   sheet.getRange(row, 11).setValue('Signed'); // Column K: Status

   sheet.getRange(row, 12).setValue(new Date().toLocaleDateString('en-IL')); // Column L: Date Signed

   sheet.getRange(row, 13).setValue(signedDocLink); // Column M: Signed Document Link

   SpreadsheetApp.flush(); // Ensure that the updates are applied immediately

   Logger.log(`Spreadsheet updated with signed document link: ${signedDocLink}`);


   // Send a copy of the signed document to the signer if configured

   if (settings.sendCopyToSigner && settings.sendCopyToSigner.toLowerCase() === 'true') {

     const emailIndex = parseInt(settings['email'], 10) - 1;

     const email = sheet.getRange(row, emailIndex + 1).getValue();

     if (email) {

       MailApp.sendEmail({

         to: email,

         subject: 'Signed Document Copy',

         body: 'Attached is a copy of your signed document.',

         attachments: [pdfBlob]

       });

       Logger.log(`Sent a copy of the signed document to: ${email}`);

     }

   }


   // Show popup to confirm successful signature

   return HtmlService.createHtmlOutput('<script>alert("Signature saved successfully!"); google.script.host.close();</script>');

 } catch (error) {

   Logger.log(`Error in saveSignatureWithPdf: ${error.message}`);

   throw error;

 }

}


// Retry operation with retries and delays

function retryOperation(func, retries = 3, delay = 1000) {

 for (let i = 0; i < retries; i++) {

   try {

     return func();

   } catch (error) {

     Logger.log(`Retry ${i + 1} failed: ${error.message}`);

     if (i === retries - 1) throw error; // Rethrow after final attempt

     Utilities.sleep(delay); // Wait before retrying

   }

 }

}


// Handle form submission and send the signing link

function onFormSubmit(e) {

 try {

   const formRow = e.range.getRow();

   const formValues = e.values;


   const settings = getSettings_();

   const emailIndex = parseInt(settings['email'], 10) - 1;

   const templateLink = settings['templateLink'];

   const email = formValues[emailIndex];


   if (!templateLink) {

     throw new Error('Template link is not specified in settings.');

   }


   // Prepare the PDF for signing

   const valuesMap = getValuesMap_(formValues, settings);

   const pdfDetails = createPdfWithPlaceholders(templateLink, valuesMap);


   // Send the signing link via email

   const subject = "בקשה לחתימה על מסמך";

   const body = `

     <div style="text-align: right; direction: rtl; font-family: Arial, sans-serif;">

       שלום,<br><br>

       אנא לחצו על הקישור הבא כדי לצפות ולחתום על המסמך שלכם:<br>

       <a href="https://script.google.com/macros/s/AKfycbz2r5A5p6kjxyBLJHNG38tBFX99gqO7Aixikm29eCGXtRhYFXrRdp6ZSr5DXx_RvHf3mA/exec?id=${pdfDetails.docId}&row=${formRow}">לחצו כאן כדי לחתום</a><br><br>

       תודה,<br>

       צוות הניהול

     </div>

   `;


   MailApp.sendEmail({

     to: email,

     subject: subject,

     htmlBody: body,

   });


   Logger.log(`Email sent to ${email} with signing link.`);

 } catch (error) {

   Logger.log(`Error on form submission: ${error.message}`);

 }

}


// Web app for capturing signature

function doGet(e) {

 const { id, row } = e.parameter;


 if (!id || !row) {

   return HtmlService.createHtmlOutput('<h2>Invalid request. Missing parameters. Please check the link provided.</h2>');

 }


 // Construct the PDF preview URL using the Google Drive embedded view link

 const pdfUrl = `https://drive.google.com/file/d/${id}/preview`;


 const html = `

   <html>

     <head>

       <style>

         body {

           font-family: Arial, sans-serif;

           text-align: center;

           margin: 0;

           padding: 0;

           display: flex;

           flex-direction: column;

           align-items: center;

         }

         .iframe-wrapper {

           position: relative;

           width: 600px; /* Fixed iframe width */

           height: 800px; /* Fixed iframe height */

           overflow: hidden;

           border: 1px solid #ccc;

           box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);

         }

         .iframe-wrapper iframe {

           position: absolute;

           top: 0;

           left: 0;

           width: 100%; /* Fit container width */

           height: 100%; /* Fit container height */

           transform: scale(1); /* Ensure scaling is uniform */

           transform-origin: top left; /* Ensure scaling origin */

           border: none; /* Remove default iframe border */

         }

         canvas {

           border: 1px solid #000;

           margin: 20px auto;

           display: block;

         }

         #signature-options {

           margin: 10px 0;

         }

         footer {

           margin-top: 20px;

           font-size: 12px;

           color: gray;

         }

       </style>

     </head>

     <body>

       <h2>View and Sign the Document</h2>

       <div class="iframe-wrapper">

         <iframe src="${pdfUrl}" sandbox="allow-same-origin allow-scripts allow-popups"></iframe>

       </div>

      

       <h3>Signature Pad</h3>

       <canvas id="signature-pad" width="600" height="150"></canvas>

       <div id="signature-options">

         <button onclick="clearSignature()">Clear</button>

         <button onclick="saveSignature()">Save Signature</button>

       </div>


       <footer>Built by Meir Horwitz - meir...@gmail.com</footer>


       <script>

         const canvas = document.getElementById('signature-pad');

         const ctx = canvas.getContext('2d');

         let drawing = false;


         canvas.addEventListener('mousedown', () => (drawing = true));

         canvas.addEventListener('mouseup', () => (drawing = false));

         canvas.addEventListener('mousemove', draw);


         function draw(e) {

           if (!drawing) return;

           const rect = canvas.getBoundingClientRect();

           ctx.lineWidth = 2;

           ctx.lineCap = 'round';

           ctx.strokeStyle = 'black';

           ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top);

           ctx.stroke();

           ctx.beginPath();

           ctx.moveTo(e.clientX - rect.left, e.clientY - rect.top);

         }


         function clearSignature() {

           ctx.clearRect(0, 0, canvas.width, canvas.height);

         }


         function saveSignature() {

           const dataURL = canvas.toDataURL();

           google.script.run.withSuccessHandler(() => {

             alert('Signature saved successfully!');

             window.close();

           }).saveSignatureWithPdf('${id}', dataURL, '${row}');

         }

       </script>

     </body>

   </html>

 `;

 return HtmlService.createHtmlOutput(html).setTitle('Document Signing');

}


// Get values map from form values and settings

function getValuesMap_(formValues, settings) {

 const valuesMap = {};

 Object.keys(settings).forEach((key) => {

   if (key !== 'webAppUrl' && key !== 'signedFolderLink' && key.indexOf('Template') === -1) {

     const index = parseInt(settings[key], 10) - 1;

     if (!isNaN(index) && formValues[index]) {

       valuesMap[key] = formValues[index];

     }

   }

 });

 Logger.log('Values Map:', valuesMap);

 return valuesMap;

}



I would highly appreciate your help!
Thanks,
Meir

Reply all
Reply to author
Forward
0 new messages