Some of my users cannot view the
Case
object owing to restricted licenses. To avoid paying for service cloud licenses, I need to generate PDF snapshots of all Case records using a batch (the are many case records which unfortunately necessitates an async approach)After encountering blank redirect pages from
getContent()
, the apex docs explained my problem:This method can’t be used in:
- Triggers
- Scheduled Apex
- Batch jobs
- Test methods
- Apex email services
So after some research I almost got it working by installing a package “batchpdf” – but I am struggling to get the native printable view to render even though it works ok for visualforce pages.
Here is the the running code – it doesn’t throw any errors but the PDF is blank unless I use a visualforce page:
public class CasePrinter implements Database.Batchable<SObject>, Database.AllowsCallouts { private String m_sessionid; public CasePrinter( string sessionid ) { m_sessionid = sessionid; } public Database.QueryLocator start(Database.BatchableContext cTx) { return Database.getQueryLocator('SELECT Id FROM Case LIMIT 10'); } public void execute(Database.BatchableContext ctx, List<Case> scope) { Case c = scope[0]; string path = '/' + c.id + '/p'; BatchPDF.PageReference pr = new BatchPDF.PageReference( path ); // insert Attachment a = new Attachment(); a.Name = 'snapshot' + c.id + '.pdf'; a.Body = pr.getContentAsPDF( m_sessionid ); a.Parentid = c.id; insert a; } public void finish(Database.BatchableContext ctx) {} }
And this is how it gets invoked:
Database.executeBatch(new CasePrinter(UserInfo.getSessionId()), 1);
but I get an empty PDF.
- I have checked the source of the page and it looks like a redirect to the login page
- I have looked at
ApexPages.StandardController
but there is noprint()
method- I also tried creating a visualforce page with an
iframe
which loads the printable view eghttps://na1.salesforce.com/500d0000004Ixt5/p
.Unfortunately, none of these approaches work. Any and all assistance is much appreciated.
Answer
Here’s an idea for an overall simpler solution. First, you need a way to expose your information to a 3rd Party (your “Users” without access). This is exactly what Site
s are for. Now, creating the pages & code to properly verify users and log them in as well as creating methods to guarantee that you don’t expose data you don’t want to (sessionCheck
methods) are not simple tasks, but they’re not too complex either. And isn’t too difficult to generalize the concept so it can be reused for ANY Site you need User-Authentication for.
Anyways, here’s the main course: To view ANY record with it’s standard layout, try the apex:detail
tag. And if you need the page rendered as a PDF, use the renderAs=”pdf” attribute in the apex:page
tag.
So, the case VF page would be as simple as:
<apex:page standardController="Case" extensions="sessionCheckController" renderAs="pdf" action="{!sessionCheck}">
<apex:detail subject="{!Contact.id}"/>
</apex:page>
With controllerExtension:
public class sessionCheckController{
private final id recordId;
public sessionCheckController(ApexPages.StandardController con){
this.recordId=con.getId();
// ...
}//END init(ApexPages.StandardController con)
public pageReference sessionCheck(){
if(!MySite.isSessionValid()) // call the class that manages User-Authentication
return MySite.getLoginPage();
return null; // stay on the page if isSessionValid = true
}//END sessionCheck()
}//END sessionCheckController
So, the moral of the story is that once you build the User-Authentication Framework (Apex Class(es) + VF Pages [Login, HomePage, Logout, Register?, Forgot Password?, Error Pages, etc.]), it’s EASY to expose any Salesforce data. And this is especially EASY if you just want to show a record’s Standard Layout via apex:detail
.
Update
Check out this post
How to roll your own authentication for an external Salesforce App?
where I describe how to build the Authentication Interface I mentioned in my above solution. Also, notice there’s a link in that post to a second post where I go into even more detail on how the whole structure:
- a Custom Object to hold User/Session/Site information,
- an Apex Class called
Portal
that houses all of the static methods like:login()
,logout()
,isValidSession()
,register()
, etc. Also, have static methods to document any action we take — such as creating a “Session” record for a User documenting whatever we like. - have
Portal
as an Instance act like an instance ofApexPages.StandardController()
where thePortal
Instance
has methods that grab the User + Session info from theSessionCookie
, check to see if the session is valid, if so, populate the instance so the instance methods likegetUserId()
return actual data (because the session is verified. Note we really are mimicking what Salesforce does withApexPages.StandardController
s. It’s great for anybody trying to implement MVC.)
So, again, the idea is we have an sObject called “Portal” that tracks (at least) three types of things:
- Site settings – records of this type have a RecordType.Name = ‘
Site
‘. Store parameters here that relate to the Site. Example: an integer called Session_Length__c to be used in the static methodPortal.isSessionValid()
where a Session is “Valid” ifCalculatedSessionTime < Session_Length__c
. - User Details – records of this type have RecordType.Name = ‘
User
‘. Typically, you’ll have another object where each record of that object will need a “User” for the Portal. Example: for an ‘Account’ portal,Portal__c
User
records for the Account-portal. For the Employee-Portal, we’ll haveUser
records lookup to theEmployee__c
object. - Session info – records of this type have RecordType.Name = ‘
Session
‘. This will hold the SessionID and anything else relavent to one particular session. It will have a lookup to aPortal_User
record (Portal__c
record withRecordType.Name='User'
), and this lookup should act like a Master-Detail relationship.
That’s the basic setup for the Object, and there is a lot of room for enhancement depending on your need. I prefer having one object with many RecordType
s, but this can eloquently be done with a Custom Object per RecordType.
The next part is the build the Apex Class, Portal
, to do all the work. First, have any general action be a static
method (like it should be). Examples are generic actions any Authentication Interface will have to do: createUser()
, register()
, isSessionValid()
, login()
, changePassword()
, logout()
, createNewSession()
, etc. (As you walk through Use Cases, it’ll become evident which methods you need. A Portal-User will get to the Login Screen, then they’ll need to login. Then when they get sent to the Home page, we’ll need to verify the Session, . . . )
Then, the idea is to have an instance of Portal
act like an instance of ApexPages.StandardController
. So, the constructor will try to get the Username
and SessionId
out of the SessionCookie
, and do the SOQL queries to get the UserId
+ whatever else you want to have handy as instance methods
.
Another idea I’ve had for implementation is create an interface
for all Portal Controllers.
public interface PortalInterface{
pageReference sessionCheck();
}
How would I use this? Suppose we have the Visualforce Page Portal_HomePage
using Controller Con_Portal_HomePage
.
Portal_HomePage:
<apex:page controller="Con_Portal_HomePage" action="{!sessionCheck}">
Con_Portal_HomePage
public class Con_Portal_HomePage implements PortalInterface{
private Portal portalController;
private id userId;
public Con_Portal_HomePage(){
this.portalController=Portal.getNewPortalController();
}//END init()
private void customInit(){
// things to initialize if we have a valid session
this.userId=portalController.getUserId();
}//END customInit()
public pageReference sessionCheck(){
pageReference sessionCheckResult=portalController.getSessionCheck();
if(sessionCheckResult==null){
// then the session is valid / stay on the same page
this.customInit();
}
return sessionCheckResult;
}//END sessionCheck
Now, I could probably jazz this up by creating a Visualforce Template that calls the action method sessionCheck()
, but the point is, once logged in, you’ll need to verify the session every time a user goes to a different page in your “portal” / Site
.
Again, check out the post in the link given above, and make sure to check out the link to the second post too. If you’re still reading this, it’s probably worth the look. Though this will take some work to initially build, the ideas behind the entire implementation are fairly simple, and the real work lies in mapping out all of the possible Use Cases. And once you have the framework built, it’s highly reusable.
Maybe this idea will be packaged up when I get a chance to finely tune it.
Attribution
Source : Link , Question Author : max , Answer Author : Community