[Aura] ゲストユーザがアップロードしたファイルをレコードに紐付ける

<!-- TODO -->
<!-- done:★共有設定がされていない場合も、内部ユーザの場合、紐付けができるか? -->
<!-- done:★共有設定のあるゲストユーザでも紐付けできないのか? -->
<!-- done:★fileFieldNameの命名ルールを守らなかったらどうなるのか。 -->
<!-- done:★2つの方法を併用したとき。内部ユーザの場合、どのように紐づくのか。同じ値を設定してエラーにならないか。異なる値を設定して2つ設定されるのか。 -->
<!-- done:★要動作確認 -->

## はじめに

Auraコンポーネントの`lightnint:fileUpload`を使用することで簡単にファイルアップローダーを画面に表示することができます。
![fileupload.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/bc8b2b96-45cc-8127-a593-9d28c9bf4aea.png)
![fileuploading.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/34b8751e-5eac-a7fe-883e-b7d4031249b8.png)


ファイルがアップロードされるとContentDocumentとContentVersionが作成されます。

### 内部ユーザ、コミュニティユーザの場合
ユーザが内部ユーザ、コミュニティユーザの場合、アップロードしたファイルのレコードへの紐付けは、`lightning:fileUpload`の`recordId`属性にSFIDを設定することで行えます。内部的には`recordId`で設定したレコードとアップロード時に作成されたContentDocumentへの参照を持つContentDocumentLinkが作成されます。

※ただし、ログインユーザがrecordIdに指定したオブジェクトの参照権限がない場合、またはレコードが共有されていない場合、アップロードに失敗します。

```html:Aura
<lightning:fileUpload recordId="{v.recordId}"/>
```

### サイトゲストユーザの場合
ゲストユーザでもオブジェクトの参照権限[^guest-obj]と、レコードの共有設定[^guest-share]を行うと、recordIdを使用してレコードへの紐付けが可能ですが、セキュリティ上問題があります[^guest-security]。

そこで`fileFieldName`属性、`fileFieldValue`属性、ContentVersionトリガを使用してレコードへの紐付けを行います。

## 実装
### 1. ゲストユーザのファイルアップロード許可の設定を行う
サイトゲストユーザにファイルアップロードを許可する場合、[設定]-[機能設定]-[Salesforce Files]-[一般設定]の「サイトのゲストユーザにファイルアップロードを許可」にチェックします。
ExperienceCloudでファイルサイズの上限を設定する場合、[ワークスペース]-[管理]-[詳細]の「最大ファイルサイズ(MB)を設定します。
ExperienceCloudのゲストユーザでアップロードされたファイルの所有者は[ワークスペース]-[管理]-[詳細]の「レコードの所有者」に設定されたユーザになります。

### 2. ContentVersionにレコードID保存用のカスタム項目を作成する
ファイルアップロード時に、紐付け先のレコードIDを保存するためにテキストのカスタム項目をContentVersionに作成します。
API参照名は`Guest_Record_fileupload__c`のように、`fileupload__c`で終わるようにする必要があります([開発者ガイド](https://developer.salesforce.com/docs/component-library/bundle/lightning:fileUpload/documentation))。

※ContentVersionへのカスタム項目追加はClassicでは行えません。LEXのオブジェクトマネージャから追加する必要があります。

### 3. Auraコンポーネントに`lightning:fileUpload`を配置する
`fileFieldName`に、ContentVersionに作成したfileupload__cで終わるカスタム項目のAPI参照名を設定します。この規則に従わない項目名を設定してもエラーにはなりませんが、レコードに値は設定されません。
fileFieldValueに、紐付け先のレコードIDを設定します。

※`recordId`属性と`fileFieldValue`属性は併用可能です。同じ値が設定された場合でもエラーにはなりません。異なる値を設定した場合、両方のレコードにContentDocumentLinkが作られます(recordIdで指定したレコードにアクセス権がある場合)。

```html:Aura
<lightning:fileUpload label="Attach receipt"
    name="fileUploader"
    recordId="{!v.recordId}"
    fileFieldName="Guest_Record_fileupload__c"
    fileFieldValue="{!v.parentId}"/>
```

### 4. ContentVersionトリガでアップロードファイルのレコードへの紐付けを行う
ContentVersionにトリガを作成します。[ContentDocumentLink](https://developer.salesforce.com/docs/atlas.ja-jp.object_reference.meta/object_reference/sforce_api_objects_contentdocumentlink.htm)を作成して、アップロードファイルをfileFieldValueで指定したレコードに紐付けます。

```java:ContentVersionTrigger.trigger
trigger ContentVersionTrigger on ContentVersion (after insert) {
    List<ContentDocumentLink> links = new List<ContentDocumentLink>();
    for (ContentVersion newObj : Trigger.new) {
        if (newObj.Guest_Record_fileupload__c == null) { continue; }
        links.add(new ContentDocumentLink(
            ContentDocumentId = newObj.ContentDocumentId,
            LinkedEntityId = newObj.Guest_Record_fileupload__c,
            Visibility = 'AllUsers',
            ShareType = 'v'
        ));
    }
    insert links;
}
```

## Appendix
### アップロードしたファイルのIDをjavascriptで取得する
onuploadfinishedでContentVersionのId、ContentDocumentのId、ファイル名を取得することができます。
紐付け先のレコードにアクセス権がない場合、documentIdはundefinedになります。

```html:aura_cmd
<lightning:fileUpload label="Attach receipt"
    name="fileUploader"
    recordId="{!v.recordId}"
    fileFieldName="Guest_Record_fileupload__c"
    fileFieldValue="{!v.parentId}"
    onuploadfinished="{!c.doUploadFInished}"/>
```

```javascript:aura_js
doUploadFinished : function(component, event, helper) {
    let files = event.getParam("files");
    files.forEach(file => {
        console.log(`ContentVersionId: ${file.contentVersionId}`);
        console.log(`ContentDocumentId: ${file.documentId}`);
        console.log(`Title: ${file.name}`);
    });
}
```

![douploadfinished.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/0f042643-6cac-07d7-4de1-5982fcf80926.png)


## 参考
- [lightning:fileUpload | Lightning Component Library](https://developer.salesforce.com/docs/component-library/bundle/lightning:fileUpload/documentation)
- [ContentDocumentLink | Salesforce および Lightning プラットフォームのオブジェクトリファレンス](https://developer.salesforce.com/docs/atlas.ja-jp.object_reference.meta/object_reference/sforce_api_objects_contentdocumentlink.htm)


<!-- 脚注 -->
[^guest-obj]: ゲストユーザのオブジェクト権限は[ワークスペース]-[管理]-[ページ]-[Force.comに移動]-[公開アクセス設定]-[編集]から変更可能です。
[^guest-share]: ExperienceCloudのゲストユーザにレコードを共有するには、[設定]-[セキュリティ]-[共有設定]でルールタイプが「条件に基づくゲストユーザアクセス」のカスタムの共有ルールを作成します。ただし、ゲストユーザにレコードを共有するのはセキュリティ上避けるべきです。
[^guest-security]: Auraエンドポイントを使用したクラッキング https://www.enumerated.de/index/salesforce