+ Start a Discussion

getContent() in @future method for Force.com Site Guest User

Hello all,

I have a requirement to send a VF page as a PDF attachment and also add the PDF attachment to the related Opportunity in Salesforce.  This needs to be done via a public facing Force.com site.  

The site has a VF page that is a form for a customer to fill out, and the submit button performs an update to the Salesforce records(Account, Contacts, Opportunity, and a Custom Object) with the customers input.  The generateContractPdf() method below is then called to fulfill the PDF requirement mentioned above, which is a PDF formatted version of the form the customer filled out.

I am able to fulfill both requirements when running as an Admin, but through the Force.com site, the PDF attachment is created but will not open.("Failed to load PDF" or "format error: not a PDF or corrupted" error).  

If I remove the @future annotation, the PDF loads fine, however, the updates made from the original VF page controller(customer input) are not reflected on the resulting PDF.

If I change getContent() to getContentAsPdf(), the PDF will open, but it is just a blank page.

Any help is greatly appreciated.
public static void generateContractPdf(String oppId, String contactId, String businessName, Boolean requestedEmail) {

        Blob body;
        String fileName = businessName.replaceAll(' ', '_').replaceAll(',', '_').replaceAll('//', '_').replaceAll('/', '_') + 
            + '_Service_Agreement_' 
            + System.now().format('MM/dd/yyyy HH:mm:ss','America/Denver')
            + '.pdf';

        try {

            PageReference pdf = new PageReference('/apex/ContractPDFVersion1');
			pdf.getParameters().put('id', oppId);
            body = Test.isRunningTest() ? Blob.valueOf('Test') : pdf.getContent();

            ContractForm_Controller.sendAndAttachPdf(body, fileName, oppId, contactId, requestedEmail);

        } catch (VisualforceException e) {

            Error_Log__c error = new Error_Log__c(
                Error__c = 'There was an error creating the Contract PDF: ' + e,
                Error_Type__c = 'Contract PDF Creation Error',
                Opportunity__c = oppId);
            insert error;

    public static void sendAndAttachPdf(Blob body, String fileName, String oppId, String contactId, Boolean requestedEmail) {

        ContractEmailTemplate__c contractEmailTemplateID = ContractEmailTemplate__c.getValues('Services');

        Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment();
        attachment.body = body;

        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setFileAttachments(new Messaging.EmailFileAttachment[] { attachment }); 

        Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
        message.setSubject('Thank you for your business!');
        message.setPlainTextBody('Thank you for signing up');
        message.setFileAttachments(new Messaging.EmailFileAttachment[] { attachment });
        message.setToAddresses(new String[] { 'example@example.com' });

        try {
            if(requestedEmail) {
                Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail,message });

            } else {
                Messaging.sendEmail(new Messaging.SingleEmailMessage[] { message });

        } catch (Exception e) {
            Error_Log__c error = new Error_Log__c(
                Error__c = 'There was an error sending the Contract PDF email: ' + e.getMessage(),
                Error_Type__c = 'Contract PDF Email Error',
                Opportunity__c = oppId);
            insert error;

        try {

            Attachment accountAttachment = new Attachment();
            accountAttachment.ParentId = oppId;
            accountAttachment.Name = fileName;
            accountAttachment.Body = body;
            insert accountAttachment;

        } catch (Exception e) {

            Error_Log__c error = new Error_Log__c(
                Error__c = 'There was an error attaching the Contract PDF to the Opportunity: ' + e,
                Error_Type__c = 'Contract PDF Attachment Error',
                Opportunity__c = oppId);
            insert error;

Andy BoettcherAndy Boettcher
You mention that this works 100% just fine as as Admin, correct?

If that's the case, I would start looking at permissions - specifically if the Guest User has access to ContractPDFVersion1 and all of the Apex that powers that?
Shashi PatowaryShashi Patowary

It's very difficult without checking the complete code. But I am guessing only - is it because force.com user doesn't have access to opportunity. Can you please try to change your controller secuirty to without sharing.

Please let me know if it helps.

Thanks Andy,

That is correct, it works fine as an Admin.

I have checked the Guest User permissions and I have added all the related Apex class and Object permissions.  

To test it, I tried accessing the ContractPDFVersion1 VF page directly as the Guest User, and it loads fine in the browser.  

Also, it saves as a PDF as expected if I remove the @future annotation, so I was thinking that ruled out permissions, unless there is some permission related to future methods or callouts??
Thanks Shashi,

I tried that as well...  I changed both controllers to without sharing, but get the same result.
Andy BoettcherAndy Boettcher
If you enable a log trace on the Sites Guest User - can you see any errors in the log when it's generating?

There aren't any errors in the debug log.  It appears that everything completes successfully, including the email being sent and the attachment being added to the Opportunity.  The only issue is when the attachment is opened, there is nothing there.
Andy BoettcherAndy Boettcher
I've been googling around and have found a number of "getContent is null from Sites" and it looks like there was some sort of certificate mismatch that caused it.  Not much on what they DID to fix it yet...

That's so weird - it works EVERYWHERE else except for when you call it asynchronously via getContent()...and it's returning null.

Just for giggles - in your PageReference, throw your Id right inline with the (/apex/ContractPDFVersion1?id=' + oppId) and see if maybe that helps?
Thanks Andy,

I tried adding the Id to to PageReference, but get the same results.

I was reading this post about a workaround(supposedly not needed after Winter '16), but I'm wondering if I would run into the same issue with sites.

From the looks of the comments, it appears the workaround doesn't work for sites:

User-added image
Emily Davis 13Emily Davis 13
We were originally implementing the workaround suggested in the blog post you linked to, and it actually worked great for site guest users. You are right, that workaround is no longer needed after Winter '16, and I believe it won't be usable at all after implementing the critical update "PageReference getContent() and getContentAsPdf() Methods Behave as Callouts" (http://releasenotes.docs.salesforce.com/en-us/summer15/release-notes/rn_vf_getcontent_callout_cruc.htm?edition=&impact=). The last I heard was Salesforce has delayed this update until Summer '16 (http://docs.releasenotes.salesforce.com/en-us/spring16/release-notes/rn_vf_pagereference_getcontent_cruc.htm), but I'm not sure about that. When we activate that update in our org, the workaround from Jitendra's blog post gives an error. We get an error message that we cannot call a callout within a callout (since getContent() is treated as a callout and is being called from an HTTP callout).

So, for us, the site guest user doesn't work using the method you're using above. The site guest user does, however, work when using the workaround you mentioned, but that workaround probably won't be viable after Salesforce activates its critical update. :/

Does that make sense? We are still working on a workaround... I'll update you if I find something.
Beau Anderson 43Beau Anderson 43
Not sure if this will help. I´ve found that the current user needs create permission to the object where information is being retrieved. I am using getcontent to get the XML and picklist values for custom fields on the Product object. If the current user profile has read permission to an object, the picklist values aren´t passed. If the current user profile has read and create then the values are passed. So the issue might not have to do with getcontent but rather how Salesforce evaluates acccess to the content.

Raunak KumarRaunak Kumar
Dear All,

I came across this issue much late than you guys but i figured out why the PDF content was coming blank when it was logged in as guest user or the code was ran using the guest User.

Here is the solution for that...

The Guest User is not able to directly access the visualforce page which we render as PDF. So in order to provide access to it we have to construct a URL for it.If the Guest User finds the path through its URL it will support the call getContentAsPDF(); and values will be present in the PDF.

Dennis WilsonDennis Wilson
I too came across this thread after struggling too long.

Objective was automatically generating an email with PDF attachment when a record is changed to a certain status by a Site Guest User.

Constructed a VF page + controller to generate the PDF.

Trigger called a future class method to contruct message with PDF attachment.  Symptoms were:

For Site Guest Users only:
PageReference.getContent() would return HTML body ("corrupted PDF").
PageReference getContentAsPDF would return blank PDF.

Of course things would work fine with authenticated users.

The problem is (as Raunak indicated) the VF page reference in the context of the running user.

For authenticated users the Page.MyPageName constructor resolved fine.

For Site Guest Users the Page reference must be through the full site URL.

Its strange that in this particular instance the Page reference doesn't resolve correctly for the different types of users.
Emily Davis 13Emily Davis 13
Hey, all,
Thanks for your feedback on this one! Beau correctly identified the issue we were encountering... the guest user didn't have access to the record we were using to store the HTML content for the document (an object we created called "Temporary Document") . In order to give the guest user access to this Temporary Document record (without sharing all Temporary Documents), we had to:
1. As the running guest user, generate a unique key and store this key in a field on the Temporary Document record when the user inserts it.
2. Pass this unique key value into the url for the pdf page.
3. Run the controller for the pdf page in "without sharing" mode. In the pdf page's controller, query for the Temporary Document with the key matching the url parameter.

This solution enabled us to share the appropriate pdf content with guest users.