2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Aura]ゲストユーザで問合せフォームに添付ファイルをアップロードする方法

Last updated at Posted at 2022-03-11

1. はじめに

この記事に辿り着いた方はご存知と思いますが、Experience Cloud(旧Community Cloud)においてゲストユーザはデフォルトではファイルをアップロードできません。
また、フローだけを使ってコーディング無しで要件を満たすことも2022/3時点では実現出来ません。
詳細理由とフローをベースにしたゲストユーザのファイルアップロード&レコードとの紐付け方法は、コチラをご確認下さい。
この記事では、Lightning Aura ComponentとApexのみでゲストユーザによるファイルのアップロードと新規作成したレコードとの紐付けを1トランザクションで実行する方法を範囲とします。
レコード作成フォームをフルカスタマイズしたい場合を仮定し、フローを使わない方式でやってみます。

2. サイトの公開アクセス

作成したサイトは、不特定多数の人へ公開する機能がExperience Cloudにはあります。
デフォルトでは非公開になっていますが、以下のように設定することで公開されます。
公開状態になるとゲストユーザ用のURLが表示されます。
公開サイトのことを、この記事ではゲストユーザがアクセス可能な状態と表現します。
エクスペリエンスビルダー > 歯車(設定) > 全般タブ > サイトの詳細セクション
公開アクセス

3. ゲストユーザへのファイルアップロード許可

ゲストユーザには、デフォルトではファイルのアップロード行為が許可されておらず、明示的に許可する設定を行う必要があります。
以下設定項目の吹き出しを確認すると、「lightning:fileUpload」なるコンポーネントを使用するとアップロードが出来るとあります。
エクスペリエンスビルダーで提供されている標準のコンポーネントでは、ライセンスを持ったユーザ向けにのみアップロード機能があります。しかし、ゲストユーザ向けのサンプルコンポーネントにはアップロード機能はありません!
つまり、「lightning:fileUpload」を使って開発をしなければならないということです。
"lightning:"とある様にLightning Aura Componentで開発しないと、ゲストユーザファイルアップロードが出来ません。
設定 > 機能設定 > Salesforce Files > 一般設定
Salesforce Files

4. lightning:fileUploadコンポーネント利用

先ほどヒントがあった「lightning:fileUpload」をGoogleで検索すると、Aura Componentのサンプルコードが出てきます。
LWCでは「lightning-file-upload」コンポーネントが同機能を提供するようですが、本記事の対象からは割愛します。
サンプルコードに従いauraを採用します。
読み進めると、Guest_Record_fileupload__cに紐付けたいレコードをセットすると、アップロードと同時に紐付けが行われるとあります。
レコード編集時ならともかく、新規レコード作成時にはIDはまだ発番されていません。
新規レコードに関連付ける為には、予めアップロードしたファイルID情報が必要であると考えることが出来ます。
アップロードしたファイルIDを内部で保持しておき、レコードが作成された後で紐付けを行う方法にします。
以上より公式ドキュメントに反し、ContentVersionオブジェクトにGuest_Record_fileuploadというカスタム項目は作らずに進めます。

lightning:fileUpload利用例
<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId">
    <aura:attribute name="filetype" type="List" default="['.png', '.jpg', '.jpeg']" />
    <aura:attribute name="multiple" type="Boolean" default="true" />
    <aura:attribute name="disabled" type="Boolean" default="true" />
    <aura:attribute name="recordId" type="String" />
    <aura:attribute name="encryptedToken" type="String" />
    <lightning:fileUpload label="Attach receipt"
        name="fileUploader"
        multiple="true"
        accept="{!v.filetype}"
        recordId="{!v.recordId}"
        fileFieldName="Guest_Record_fileupload__c"
        fileFieldValue="{!v.encryptedToken}"
        onuploadfinished="{!c.handleUploadFinished}" />
</aura:component>

5. ファイル関連オブジェクトリレーション確認

紐付け処理を検討するにあたり、ファイルオブジェクトのリレーションを公式ドキュメントから確認します。
必要な部分を、必要なアトリビュートに絞って抜き出すと、以下のER図になります。
ファイルをアップロードすると、最新バージョンを管理するContentDocumentと、ファイルの実体であるContentVersionが作成されます。
そして、ContentDocumentLinkオブジェクトを連関オブジェクトとして、ファイルとレコードを関連付ける仕組みであると読み取れます。
LinkedEntityIdには、ケースだけではなく任意のレコードのIDを指定出来る様になっています。
Content Document Link
ファイルをアップロードしたタイミングで取得できる(筈の)DocumentIdと、ケースを作成した際に得られるCase Idが分かれば紐付けは出来そうです。

新規作成レコードにファイルを紐付ける順序(想定)
 ①ファイルアップロード時にDocumentIdを取得する
 ②ケースを登録し、CaseIdを取得する
 ③ケース登録直後に、DocumentIdとCaseIdを紐づけるContentDocumentLinkレコードを作成する

6. lightning:fileUploadを使ったアップロード実験

以下javascriptで、アップロードしたファイルのContentDocumentIDを取得できる事を確認してみます。①の実証実験です。

({
    //アップロード後処理
    handleUploadFinished: function (component, event, helper) {
        
        var uploadedFiles = event.getParam("files");
        
        uploadedFiles.forEach(file => {
            console.log("contentVersionId:"+file.contentVersionId);
            console.log("documentId:"+file.documentId); 
            console.log("name:"+ file.name);
        });     
    }
})

ファイルをアップロードして、ブラウザのコンソールを確認してみると、、、
Console
あれ、undefined? ..documentId取れてない..?
どうやら、ただではDocumentIdは取れない様です。
ER図に立ち戻って、作戦を練り直します。
ContentVersionIdが分かれば、リレーションを辿ってContentDocumentIdを突き止める事が出来そうです。
では、以下の様に作戦を変更します。

新規作成レコードにファイルを紐付ける順序(改)
 ①ファイルアップロード時にContentVersionIdを取得する (ContentDocumentIdは、取れない..)
 ①’SOQLを使って、ContentVersionIdをWhere句に指定し、ContentDocumentIdを取得する
 ②ケースを登録し、CaseIdを取得する
 ③DocumentIdとCaseIdを紐づけるContentDocumentLinkを挿入する

7. ContentDocumentLinkによるレコードとファイルの関連付け処理

先ほどアップロードしたファイルのContentVersionIdと、適当な新規作成ケースのIdを引数に指定して、紐付けられるか実験してみます。
ContentVersionオブジェクトの中から、ContentVersionIdを指定して対象のレコードを取り出します。①'~③の実証実験です。
以下コードのSOQL部は、取り出したレコードの1属性であるContentDocumentIdを取得しています。
リファレンスによると、「外部ユーザは Visibility を AllUsers にのみ設定できます」とあるので、選択の余地はありません。
ケースIdと、ContentDocumentIdを指定してContentDocumentLinkレコードを作成する流れです。
Apexを実行。ケースレコード画面をリロードすると。。。意図したとおりにファイルが紐付いています。良かった良かった
これで、①~③を連続して処理することでケース作成&アップロード済みファイルの関連付けが出来ることが証明されました。

AttachmentController.cls
public without sharing class AttachmentController {
    

    public static void CreateContentDocumentLink(Case newCase, List<String> documents) {
        
        //ケース登録
        insert newCase;
        //紐づけオブジェクト宣言
        List<ContentDocumentLink> newLinks = new List<ContentDocumentLink>();
        
        //添付ファイル分ループ
        for(String docverId : documents) {
            
            //ContentVersionIdを元に、CondentDocumentIdを取得
            ContentVersion cv = [SELECT Id,ContentDocumentId FROM ContentVersion WHERE Id =:docverId];
            
            ContentDocumentLink newLink = new ContentDocumentLink(
                ContentDocumentId = cv.ContentDocumentId,
                LinkedEntityId = newCase.Id, //発番されたケースID
                Visibility = 'AllUsers',
                ShareType = 'v'                    
            );
            newLinks.add(newLink);
        }
        
        //紐づけオブジェクト挿入
        insert newLinks;           
    }
}

8. ゲストユーザへApexClassの実行を許可

ケース登録の流れでゲストユーザがApexを実行出来る様に、プロファイルへ直接対象Classの実行権限を与えます。
「ユーザ > プロファイル」からゲスト用プロファイルを探すと、何故か対象プロファイルは見つかりません。
お気づきの方も居るかと思いますが、2章のエクスペリエンスビルダーの公開アクセス設定の下に、ゲスト用プロファイルへの入口があります。
Guest Profile

9. Aura Componentでケースフォーム画面準備

メールアドレス、氏名、件名、説明の4つの入力ボックスのあるフォームを用意します。
以下画像の様な感じにします。
Form
フォーム内に、アップロードコンポーネントを含めているのがポイントです。
Aura Componentの記載は、こんな感じです。

GuestInquiryForm.cmp
<aura:component implements="forceCommunity:availableForAllPageTypes"
                access="global" 
                controller="AttachmentController">
    
    <aura:attribute name="newCase" type="Case" default="{ 'Type': 'Question',
                                                          'Origin':'コミュニティ',
                                                          'Status':'New',                                                        
                                                          'SuppliedEmail' : ''},
                                                          'SuppliedName' : ''},
                                                          'Subject' : ''},
                                                          'Description' : ''}"/>
    <aura:attribute name="documents" type="List" default="[]"/>
    <aura:attribute name="filenames" type="List" default="[]"/>
    <aura:attribute name="accept" type="List" default="['.jpg', '.jpeg', '.png', '.gif', '.bmp','.xls','.xlsx','.zip','.lzh']"/>
    <aura:attribute name="multiple" type="Boolean" default="true"/>
    <aura:attribute name="disabled" type="Boolean" default="false"/>
    <aura:attribute name="isComplete" type="Boolean" default="false"/>

	<article class="slds-card slds-m-horizontal_medium ">
        <div class="slds-grid slds-einstein-header slds-card__header">
            <header class="slds-media slds-media_center slds-has-flexi-truncate">
                <div class="slds-grid slds-grid_vertical-align-center slds-size_3-of-4 slds-medium-size_2-of-3">
                    <div class="slds-media__body">
                        <h2 class="slds-truncate" title="Einstein (10+)">                          
                                <span class="slds-text-heading_small">MKIサポートセンター</span>                            
                        </h2>
                    </div>
                </div>
                <div class="slds-einstein-header__figure slds-size_1-of-4 slds-medium-size_1-of-3"></div>
            </header>
        </div>
        
        <aura:if isTrue="{!not(v.isComplete)}">
            <div class="Create slds-m-horizontal_medium slds-m-top_medium">      
                <lightning:card class="headerLabel" iconName="action:new_case" title="お問い合わせフォーム">  
                    <form class="slds-form--stacked">  
                        <lightning:input aura:id="SuppliedEmail" 
                                             label="メールアドレス" 
                                             name="SuppliedEmail"
                                             value="{!v.newCase.SuppliedEmail}"
                                             required="true"
                                             class="input slds-m-horizontal_large slds-m-vertical_medium slds-m-top_x-large"
                                             maxlength="80"/>
        
                        <lightning:input aura:id="SuppliedName" 
                                             label="氏名" 
                                             name="SuppliedName"
                                             value="{!v.newCase.SuppliedName}"
                                             required="true"
                                             class="input slds-m-horizontal_large slds-m-vertical_medium"
                                             maxlength="80"/>
                     
                        <lightning:input aura:id="Subject" 
                                             label="件名" 
                                             name="Subject"
                                             value="{!v.newCase.Subject}"
                                             required="true"
                                             class="input slds-m-horizontal_large slds-m-vertical_medium"
                                             maxlength="80"/>
        
                         <lightning:textarea aura:id="Description"
                                             name="Description"
                                             label="説明"
                                             value="{!v.newCase.Description}"
                                             class="input slds-m-horizontal_large slds-m-top_medium"
                                             required="true"/>
                        
                         <div class="input slds-m-horizontal_large">
                             <lightning:fileUpload name="fileUploader"
                                             multiple="{!v.multiple}"
                                             accept="{!v.accept}"
                                             disabled="{!v.disabled}"
                                             onuploadfinished="{!c.onUploadFinished }"/>
                        </div>  
                        
                        <aura:iteration items="{!v.filenames}" var="doc">
                            <p class="slds-m-horizontal_large slds-text-title_bold ">{!doc}</p>
                        </aura:iteration>
                        
                        <div class="slds-m-vertical_large slds-align_absolute-center">
                        <lightning:button label="登録"
                                class="communityBtn slds-button custombtn"
             
                                          variant="brand"
                                onclick="{!c.SubmitCase}"/>
                        </div>
                    
                    </form>
                </lightning:card>
            </div>
            
            <aura:set attribute="else">
                  <p class="SuccessPanel">
                    <lightning:card title="" class="SuccessPanel vertical-center">
                        <div class="slds-list_horizontal slds-align_absolute-center slds-m-bottom_large">
                            <lightning:icon iconName="utility:success" alternativeText="Success!" variant="Success"
                                    title="success variant xx-small" size="xx-small" />
                            <h2 class="slds-text-title_bold"> 正常に送信されました</h2>
                        </div>     
                        <h2 class = "slds-m-bottom_large">弊社サポートよりご回答申し上げますので、今しばらくお待ち下さい</h2>          
                        <lightning:button label="閉じる" title="close button"  class="slds-m-left_x-small buttonwide"/>
                    </lightning:card>
                </p>
            </aura:set>          
        </aura:if>         

        <footer class="slds-card__footer">
        </footer>
    </article>   
</aura:component>

ファイルをアップロードする度に、コントローラーのonUploadFinishedを実行しています。
登録ボタンをクリックすると、コントローラーのSubmitCase経由で、Apexクラス(ケースとファイルの紐付け)を実行する仕掛けです。
ケースとファイルの紐付けまで正常に終了すると、aura ifによりthank youコンポーネントに切り替えます。

10. Auraコントローラ処理内容

コントローラには、コンポーネントから呼び出される関数を2つ定義します。
一つ目は、アップロードが完了する度に呼ばれるonUploadFinishedです。
アップロードされたファイルのDocumentVersionIdを、後で紐付ける為にリストに追加していきます。
今現在どのファイルがアップロード済みなのかをユーザに表示する為に、ファイル名もリストに追加します。
これらリストは、コンポーネントアトリビュートとして定義されています。
(一度アップロードしたファイルのキャンセル処理は省略)

GuestInquiryFormController.js
onUploadFinished: function (component, event, helper) {

    var uploadedFiles = event.getParam("files");

    //ファイル-レコード紐付け用
    var documentVersionIds = component.get("v.documents");
    uploadedFiles.forEach(element => documentVersionIds.push(element.contentVersionId));
    component.set("v.documents", documentVersionIds);

    //画面表示用ファイル名リスト追加    
    var filenames = component.get("v.filenames"); 
    uploadedFiles.forEach(element => filenames.push(""+element.name));
    component.set("v.filenames", filenames);
}

2つ目は、登録ボタンを押下した際に呼ばれるsubmit処理です。
ケースを登録し、発番されたCase IdとDocumentVersionIdのリストをApex Classに渡します。
紐付けが完了すると、正常メッセージを表示する為に、切替フラグisCompleteにTrueをセットしています。
フラグがTrueになると、aura ifによりコンポーネントが自動的に切り替わります。

GuestInquiryFormController.js
SubmitCase : function(component, event, helper) {

    var action = component.get("c.CreateContentDocumentLink");

    action.setParams ({
        documents : component.get("v.documents"),   //アップロードファイルリスト
        newCase : component.get("v.newCase")        //ケースオブジェクト
    });

    action.setCallback(this, function(response) {

        var state = response.getState();

        if (state==="SUCCESS") {
            //aura:ifのフラグ設定、正常終了メッセージを表示
            component.set("v.isComplete", "true");
        }
        else {
            let errors = response.getError();
            let message = 'エラーが発生しました';
            if (errors && Array.isArray(errors) && errors.length > 0) {
                message = errors[0].message;
            }
            console.error(message);
        }
    });
    $A.enqueueAction(action);
}

11. 最後に

作ったコンポーネントを、公開サイトに埋め込んで動作を確認します。
以下アニメーションの様に、ゲストユーザによりレコードが作成され、同時にアップロードファイルが無事紐付きました。
公開サイトは、不特定多数の人がアクセス出来る為、セキュリティ設計を行う必要があります。
セキュリティの考慮は、Salesforceのベストプラクティスを参考にして下さい。
sample movie

今回のサンプルコードは、リンクからダウンロードして試せます。

$\tiny{※この実験サンプルコードによよって生じた損害等に関して、一切の責任を負いかねます}$

参考

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?