Generic File Related List Lightning Component

Apex Class:

/*
@Author : Biswajeet Samal
@CreatedDate : 08th Aug 2020
@Description : Related Files Controller
*/
public class RelatedFilesController {
    
    @AuraEnabled
    public static List<ContentDocument> getRelatedDocs(Id recordId){
        List<ContentDocument> cdList = new List<ContentDocument>();
        List<ContentDocumentLink> cdlList = [SELECT ContentDocumentId FROM ContentDocumentLink
                                             WHERE LinkedEntityId = :recordId];
        Set<Id> cdIds = new Set<Id>();
        for (ContentDocumentLink cdl : cdlList) {
            cdIds.add(cdl.ContentDocumentId); 
        }        
        cdList = [SELECT Id, Title, FileType, OwnerId, Owner.Name, CreatedDate,
                  CreatedById, CreatedBy.Name, ContentSize
                  FROM ContentDocument WHERE Id IN :cdIds];
        return cdList;
    }
    
    @AuraEnabled
    public static string getDocURL(Id docId){
        ContentVersion cv = [SELECT Id FROM ContentVersion WHERE ContentDocumentId = :docId AND IsLatest = true];
        String cvDownloadURL = URL.getSalesforceBaseUrl().toExternalForm() + '/sfc/servlet.shepherd/version/download/' + cv.Id;
        return cvDownloadURL;
    }
    
    @AuraEnabled
    public static void deleteDoc(Id docId){
        ContentDocument conDoc = new ContentDocument(Id = docId);
        delete conDoc;
    }
}

Lightning Component:

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,forceCommunity:availableForAllPageTypes,force:lightningQuickAction" access="global" controller="RelatedFilesController">
    
    <!--Attributes-->
    <aura:attribute name="cdList" type="List"/>
    
    <!--Handlers--> 
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <!--Component Start-->
    <table class="slds-table slds-table_cell-buffer slds-table_bordered">
        <thead>
            <tr class="slds-line-height_reset">
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="Title">Title</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="File Type">File Type</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="Created By">Created By</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    <div class="slds-truncate" title="Created Date">Created Date</div>
                </th>
                <th class="slds-text-title_caps" scope="col">
                    
                </th>
            </tr>
        </thead>
        <tbody>
            <aura:iteration items="{!v.cdList}" var="cd">
                <tr>
                    <th scope="row">
                        <div class="slds-truncate" title="{!cd.Title}">
                            <a onclick="{!c.handleSelectedDocPreview}" data-Id="{!cd.Id}">{!cd.Title}</a>
                        </div>
                    </th>
                    <th scope="row">
                        <div class="slds-truncate" title="{!cd.FileType}">
                            {!cd.FileType}
                        </div>
                    </th>
                    <th scope="row">
                        <div class="slds-truncate" title="{!cd.CreatedBy.Name}">
                            <a onclick="{!c.handleRedirectToUserRecord}" data-Id="{!cd.CreatedById}">{!cd.CreatedBy.Name}</a>
                        </div>
                    </th>
                    <th scope="row">
                        <div class="slds-truncate" title="{!cd.CreatedDate}">
                            <lightning:formattedDateTime value="{!cd.CreatedDate}"/>
                        </div>
                    </th>
                    <th scope="row">
                        <lightning:buttonMenu alternativeText="Show menu" menuAlignment="auto" onselect="{!c.handleSelectedAction}" value="{!cd.Id}">
                            <lightning:menuItem value="Download" label="Download" iconName="utility:download" title="Download" />
                            <lightning:menuItem value="Delete" label="Delete" iconName="utility:delete" title="Delete"/>
                        </lightning:buttonMenu>
                    </th>
                </tr>  
            </aura:iteration>
        </tbody>
    </table>
    <!--Component End-->
</aura:component>

Lightning Component JS Controller:

({
    //Get Related Docs
    doInit : function(component, event, helper) {
        helper.getRelatedDocuments(component, event); 
    },
    
    //Redirect To User Record
    handleRedirectToUserRecord: function (component, event, helper) {
        var recordId = event.currentTarget.getAttribute("data-Id")
        var navEvt = $A.get("e.force:navigateToSObject");
        navEvt.setParams({
            "recordId": recordId,
            "slideDevName": "Detail"
        });
        navEvt.fire();
    },
    
    //Preview Selected File
    handleSelectedDocPreview : function(component,event,helper){ 
        $A.get('e.lightning:openFiles').fire({
            recordIds: [event.currentTarget.getAttribute("data-Id")]
        });
    },
    
    //Handle Selected Action
    handleSelectedAction: function(component, event, helper) {
        var docId = event.getSource().get("v.value");
        var selectedMenuValue = event.detail.menuItem.get("v.value");
        switch(selectedMenuValue) {
            case "Delete":
                helper.deleteDocument(component, event, docId);
                break;
            case "Download":
                helper.downloadDocument(component, event, docId);
                break;
        }
    }
})

Lightning Component JS Helper:

({
    getRelatedDocuments : function(component, event) {
        var action = component.get("c.getRelatedDocs");
        action.setParams({
            recordId : component.get("v.recordId")
        });
        action.setCallback(this, function(response) {
            var state = response.getState();
            if(state === "SUCCESS"){
                component.set('v.cdList', response.getReturnValue());
            }else if(state === "INCOMPLETE") {
                console.log("INCOMPLETE");
            }else if(state === "ERROR"){
                var errors = response.getError();
                if(errors){
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " +  errors[0].message);
                    }
                }else{
                    console.log("Unknown error");
                }
            }
        });
        $A.enqueueAction(action);  
    },
    
    deleteDocument : function(component, event, docId) {
        var action = component.get("c.deleteDoc");
        action.setParams({
            docId : docId
        });
        action.setCallback(this, function(response) {
            var state = response.getState();
            if(state === "SUCCESS"){
                this.getRelatedDocuments(component, event);
            }else if(state === "INCOMPLETE") {
                console.log("INCOMPLETE");
            }else if(state === "ERROR"){
                var errors = response.getError();
                if(errors){
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " +  errors[0].message);
                    }
                }else{
                    console.log("Unknown error");
                }
            }
        });
        $A.enqueueAction(action);  
    },
    
    downloadDocument : function(component, event, docId) {
        var action = component.get("c.getDocURL");
        action.setParams({
            docId : docId
        });
        action.setCallback(this, function(response) {
            var state = response.getState();
            if(state === "SUCCESS"){
                var urlEvent = $A.get("e.force:navigateToURL");
                urlEvent.setParams({
                    "url": response.getReturnValue()
                });
                urlEvent.fire();
            }else if(state === "INCOMPLETE") {
                console.log("INCOMPLETE");
            }else if(state === "ERROR"){
                var errors = response.getError();
                if(errors){
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " +  errors[0].message);
                    }
                }else{
                    console.log("Unknown error");
                }
            }
        });
        $A.enqueueAction(action);  
    }
})

Google reCAPTCHA on Visualforce Page

Step 1: Configure the Google reCAPTCHA and get the Site Key & Secret Key

  • Login to Google reCAPTCHA
  • Register a new site
  • Add Label e.g. Salesforce.com
  • Select reCAPTCHA type “reCAPTCHA v2”
  • Select “I’m not a robot” Checkbox option.
  • Add a domain e.g. yourorgurl.com
  • Accept the reCAPTCHA Terms of Service
  • Submit and get the Site Key & Secret Key

Step 2: Add Google as a Remote Site in Salesforce

Step 3: Create apex controller

/*
@Author : Biswajeet Samal
@CreatedDate : 30th JUL 2020
@Description : Google ReCAPTCHA Controller
*/
public class GoogleReCAPTCHAController {
    
    public Boolean verified { get; private set; }
    public String response  { 
        get {
            return ApexPages.currentPage().getParameters().get('g-recaptcha-response');
        }
    }
    public String firstName{get;set;}
    public String lastName{get;set;}
    public String publicKey {get;set;}
    
    private String remoteHost{
        get {
            String ret = '127.0.0.1';
            //Also could use x-original-remote-host
            Map<String, String> hdrs = ApexPages.currentPage().getHeaders();
            if (hdrs.get('x-original-remote-addr')!= null)
                ret = hdrs.get('x-original-remote-addr');
            else if (hdrs.get('X-Salesforce-SIP')!= null)
                ret = hdrs.get('X-Salesforce-SIP');
            return ret;
        }
    }
    
    //Google Secret Key
    private static String secretKey = 'ADD YOUR GOOGLE RECAPTCHA SECRET KEY';
    private static String baseUrl = 'https://www.google.com/recaptcha/api/siteverify';
    
    //Constructor
    public GoogleReCAPTCHAController(){
        this.publicKey = 'ADD YOUR GOOGLE RECAPATCHA SITE KEY OR PUBLIC KEY';//Google Site Key or Public Key
        this.verified = false;
    }
    
    public PageReference submit(){
        if (response == null ){
            //Google recaptcha empty response
            return null;
        }
        HttpResponse res = getGoogleReCAPTCHAResponse(baseUrl,'secret=' + secretKey + '&remoteip=' + remoteHost + '&response=' + response);
        if (res != null ) {
            JSONParser parser = JSON.createParser(res.getBody());
            while (parser.nextToken() != null) {
                if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) && (parser.getText() == 'success')) {
                    parser.nextToken();
                    this.verified = parser.getBooleanValue();
                    break;
                }
            }            
        }   
        if(this.verified){
            //Add your logic
            return null;
        }
        else{
            //Stay on page to re-try reCAPTCHA
            return null;
        }
    }
    
    //Get Google reCAPTCHA Service Response 
    private static HttpResponse getGoogleReCAPTCHAResponse(string requestURL, string body){
        HttpResponse response = null;
        HttpRequest req = new HttpRequest();
        req.setEndpoint(requestURL);
        req.setMethod('POST');
        req.setBody (body);
        try{
            Http http = new Http();
            response = http.send(req);
            System.debug('ReCAPTCHA Response-' + response);
            System.debug('ReCAPTCHA Body-' + response.getBody());
        }
        catch(System.Exception ex){
            System.debug('ERROR Message-' + ex.getMessage());
        }
        return response;
    }
}

Step 4: Create VF Page

<apex:page controller="GoogleReCAPTCHAController" sidebar="false" showHeader="false" cache="false" id="pg">
    <script type='text/javascript' src='https://www.google.com/recaptcha/api.js'/>
    <script type='text/javascript'>
        function recaptchaCallback() {
        var btnVerify = document.getElementById("pg:fm:pb:pbb:btnVerify");
        if (btnVerify.classList.contains("hideButton") ) {
            btnVerify.classList.remove("hideButton");
        }
    }
    </script> 
    
    <style type="text/CSS">
        .hideButton{
        display:none !important;
        }
    </style>
    <apex:form id="fm">
        <apex:pageBlock title="Google Captcha Example" id="pb">
            <apex:pageBlockSection columns="1" id="pbs"> 
                <apex:pageBlockSectionItem >
                    <apex:outputLabel for="fnField" value="First Name"/>
                    <apex:inputText value="{!firstName}" id="fnField"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem >
                    <apex:outputLabel for="lnField" value="Last Name"/>
                    <apex:inputText value="{!lastName}" id="lnField"/>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem >
                    <div data-type="image" class="g-recaptcha" data-sitekey="{!publicKey}" data-callback="recaptchaCallback"></div>
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem rendered="{!verified}">
                    <p>Google reCAPTCHA verified successfully.</p>
                </apex:pageBlockSectionItem>
            </apex:pageBlockSection>
            <apex:pageBlockButtons id="pbb" location="bottom">
                <apex:commandButton action="{!submit}" styleClass="hideButton" value="Verify" id="btnVerify"/>
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>
</apex:page>