Drag & Drop Documents Widget - Need Help with File Upload and Document Creation

Hi ,
I am currently working on a project where I need to integrate a file upload feature within SuiteCRM. Here is a summary of what i have done so far and the assistance we need:

What I Have Implemented:

Drag-and-Drop Widget:

  • We have successfully created a drag-and-drop widget in the sidebar using the ngx-file-drop library.
  • This widget allows users to drag files and drop them for upload.
  • The drag-and-drop documents module helps CRM users to create new documents just by dropping the files into the Drag-and-Drop sidebar widget present in the Detail view. Users can upload multiple files at a time.

Where I Need Help:

  • I’m unsure how to handle the actual file upload process within SuiteCRM.
  • Should I use a separate endpoint or leverage the existing API for creating documents?
  • How can I create a new document record with the uploaded file and link it to the current account ?
Code Snippets:


/var/www/html/SuiteCRM/extensions/defaultExt/app/src/containers/sidebar-widget/drag-drop/drag-drop-sidebar-widget.component.html
`<scrm-widget-panel [title]="'Drag and Drop Widget'">
    <div class="drag-drop-sidebar-widget" widget-body>
        <ngx-file-drop (onFileDrop)="dropped($event)" (onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)">
            <ng-template ngx-file-drop-content-tmp let-openFileSelector="openFileSelector">
                <div class="file-drop-content">
                    <button (click)="openFileSelector()">Select Files</button>
                    <p>Drag and drop files here or click to select files</p>
                </div>
            </ng-template>
        </ngx-file-drop>
    </div>
</scrm-widget-panel>
`
/var/www/html/SuiteCRM/extensions/defaultExt/app/src/containers/sidebar-widget/drag-drop/drag-drop-sidebar-widget.component.ts

import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BaseWidgetComponent } from 'core';
import { NgxFileDropEntry, FileSystemFileEntry, FileSystemDirectoryEntry } from 'ngx-file-drop';

@Component({
  selector: 'scrm-drag-drop-sidebar-widget',
  templateUrl: './drag-drop-sidebar-widget.component.html',
  styles: []
})
export class DragDropSidebarWidgetComponent extends BaseWidgetComponent implements OnInit, OnDestroy {
  @Input() accountId: string;

  public files: NgxFileDropEntry[] = [];
  private uploadUrl = 'http://localhost/SuiteCRM/public/legacy/Api/V8/module/Documents';

  constructor(private http: HttpClient) {
    super();
  }

  ngOnInit() {}

  ngOnDestroy() {}

  public dropped(files: NgxFileDropEntry[]) {
    this.files = files;
    for (const droppedFile of files) {
      if (droppedFile.fileEntry.isFile) {
        const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
        fileEntry.file((file: File) => {
          this.uploadFile(file, this.accountId);
        });
      } else {
        const fileEntry = droppedFile.fileEntry as FileSystemDirectoryEntry;
        console.log(droppedFile.relativePath, fileEntry);
      }
    }
  }

  private uploadFile(file: File, accountId: string) {
    const formData: FormData = new FormData();
    formData.append('file', file, file.name);
    formData.append('parent_type', 'Accounts');
    formData.append('parent_id', accountId);

    this.http.post(this.uploadUrl, formData).subscribe(response => {
      console.log('File uploaded successfully', response);
    }, error => {
      console.error('File upload error', error);
    });
  }

  public fileOver(event: any) {
    console.log(event);
  }

  public fileLeave(event: any) {
    console.log(event);
  }
}

I’m using SuiteCRM version8.I’ve reviewed the API documentation but couldn’t find a clear approach for file uploads.`Any insights or suggestions on how to achieve file upload and document creation with the SuiteCRM API would be greatly appreciated. Thanks!

Doesn’t the widget have a way to connect it to a back-end Angular action?

I’ve tried implementing a custom service to handle file uploads and document creation, including association with the account.

/var/www/html/SuiteCRM/extensions/defaultExt/app/src/services/UploadMultipleFilesService.php
<?php

namespace App\Custom\Services;

use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Document;
use DocumentRevision;

class UploadMultipleFilesService
{
    private $uploadDir;

    public function __construct(string $uploadDir)
    {
        $this->uploadDir = $uploadDir;
    }

    public function uploadFiles(array $files): array
    {
        $uploadedFileNames = [];

        foreach ($files as $file) {
            if ($file instanceof UploadedFile) {
                $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
                $safeFilename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $originalFilename);
                $extension = $file->getClientOriginalExtension();
                $newFilename = $safeFilename . '-' . uniqid() . '.' . $extension;

                try {
                    $file->move($this->uploadDir, $newFilename);
                    $uploadedFileNames[] = $newFilename;
                } catch (FileException $e) {
                    error_log('FileException: ' . $e->getMessage());
                    throw new \Exception('The file "' . $file->getClientOriginalName() . '" was not uploaded due to an unknown error.');
                }
            } else {
                error_log('Error: The file is not an instance of UploadedFile.');
                throw new \Exception('The file "' . $file->getClientOriginalName() . '" is not a valid uploaded file.');
            }
        }

        return $uploadedFileNames;
    }

    public function handleUpload(array $file, $accountId)
    {
        $uploadedFile = new UploadedFile(
            $file['tmp_name'],
            $file['name'],
            $file['type'],
            $file['size'],
            $file['error']
        );

        $uploadedFiles = $this->uploadFiles([$uploadedFile]);

        // Associate the uploaded file with the account
        foreach ($uploadedFiles as $uploadedFileName) {
            $this->associateFileWithAccount($uploadedFileName, $accountId);
        }

        return $uploadedFiles;
    }

    private function associateFileWithAccount(string $fileName, $accountId)
    {
        global $db;

        // Create a new document record in SuiteCRM
        $document = new Document();
        $document->document_name = $fileName;
        $document->filename = $fileName;
        $document->file_mime_type = mime_content_type($this->uploadDir . '/' . $fileName);
        $document->file_ext = pathinfo($fileName, PATHINFO_EXTENSION);
        $document->assigned_user_id = '1'; /

        // Save the document record
        $document->save();

        // Create a new document revision
        $revision = new DocumentRevision();
        $revision->document_id = $document->id;
        $revision->revision = '1';
        $revision->filename = $fileName;
        $revision->file_mime_type = mime_content_type($this->uploadDir . '/' . $fileName);
        $revision->file_ext = pathinfo($fileName, PATHINFO_EXTENSION);
        $revision->save();

        // Link the document to the account
        $account = \BeanFactory::getBean('Accounts', $accountId);
        $account->load_relationship('documents');
        $account->documents->add($document);
    }
}

However, I’m facing issues with the document creation .

I’m unsure the suggested ‘back-end Angular action’ approach or if there’s a more suitable method.I am new to this. Can you please clarify?

Sorry, I got mixed up in the nomenclature, it’s called a process handler

Typically the UI actions call these, you’ll find them in many places in the code defined like this

public const PROCESS_TYPE = 'case-calculate-priority';