Creating a file field type with multiple upload and download

Like a lot of SuiteCRM the field types are customisable and you can add file type field for upload documents. This post will explain how to add a file field type.

Steps are as below,

Step 1: Create custom field and we do this by adding a new file custom/extension/modules/<module_name>/Ext/Vardefs/<file_name>.php with the following contents:

<?php

$dictionary['<module_name>']['fields']['documents'] = array(
  'name' => 'documents',
  'vname' => 'LBL_DOCUMENTS',
  'type' => 'file',
  'dbType' => 'varchar',
  'len' => '255',
  'reportable'=>true,
  'importable' => false,
);

Create language file for assign name to field and we do this by adding a new file custom/extension/modules/<module_name>/Ext/Language/<file_name>.php with the following contents:

<?php
$mod_strings['LBL_DOCUMENTS'] = 'Documents';

Step 2: Create table with following schema,

CREATE TABLE 'custom_documents' (
  'id' varchar(36) NOT NULL,
  'module_id' varchar(36) NOT NULL,
  'filename' varchar(200) NOT NULL,
  'module_name' varchar(200) NOT NULL,
  'file_mime_type' varchar(255) DEFAULT NULL,
  'deleted' tinyint(4) NOT NULL
)

Step 3: Add a reference to the JavaScript file which will be needed for event binding.

Path:   custom/modules/<module_name>/metadata/editviewdefs.php 

<?php

$viewdefs['<module_name>']['EditView']['templateMeta']['includes'] =
    array (
        array (
        'file' => 'custom/modules/<module_name>/js/editview.js',
        ),
    );
?>

Step 4: Add the JavaScript file you want to include into the location you referenced above(custom/modules/<module_name>/js/editview.js).

$( document ).ready(function() {
	var record_id = $('input[name="record"]').val();
	$('#EditView').attr('enctype','multipart/form-data');
	var x = 1; //initlal text box count
	$(".add_field_button").click(function(e){ //on add input button click
		e.preventDefault();
			x++; //text box increment
			var val = x-1;
			$(".input_fields_wrap").append('<div><input type="file" name="attachments['+val+']" id="attachments['+val+']" onclick="file_size(this.id)"/><a href="#" class="remove_field">Remove</a></div>'); //add input box
	});
		
    $(".input_fields_wrap").on("click",".remove_field", function(e){ //user click on remove text
        e.preventDefault(); $(this).parent('div').remove(); x--;
    });
	
	$(".downloadAttachment").click(function(e) {
        var fileName = $(this).attr("name");
        var name = $(this).text();
        var data = [
            {
                "id": fileName,
                "type": name.substr((name.lastIndexOf('.') + 1)),
                "module": "<module_name>",
                "folder": "<module_name>"
            }
        ];
        downloadAttachment(data);
    });
	
	$(".remove_attachment").click(function(e) {
        var fileName = $(this).attr("name");
        var name = $(this).prev().text();
        var extension = name.substr((name.lastIndexOf('.') + 1));
        var flag = confirm("Are you want to delete " + name + " attachment");
        if (flag) {
            removeAttachment(fileName, extension)
        }
    });	
});

function downloadAttachment(data) {
    var $form = $('<form></form>')
        .attr('action', 'index.php?entryPoint=<entry_point_name>&action_type=download')
        .attr('method', 'post')
        .attr('target', '_blank')
        .appendTo('body');
    for (var i in data) {
        if (!data.hasOwnProperty(i)) continue;
        $('<input type="hidden"/>')
            .attr('name', i)
            .val(JSON.stringify(data[i]))
            .appendTo($form);
    }
    $form.submit();
}

function removeAttachment(fileName, extension) {
    $.ajax({
        url: 'index.php?entryPoint=<entry_point_name>',
        type: 'POST',
        async: false,
        data: {
            id: fileName,
            extension: extension,
            module: 'itb_candidates',
			folder: '<module_name>',
			action_type: "remove"
        },
        success: function(result) {
            var data = $.parseJSON(result);
            $('[name=' + data.attachment_id + ']').prev().hide();
            $('[name=' + data.attachment_id + ']').hide();
        }
    });
}

Step 5: Set custom created filed in layout (Edit view & Detail view) using studio and find custom created field in below two files and add the following contents

Path:custom/modules/<module_name>/metadata/editviewdefs.php & custom/modules/<module_name>/metadata/detailviewdefs.php

1 => 
       array (
        'name' => 'resume',
        'studio' => 'visible',
        'label' => 'LBL_RESUME',
	'customCode' => '{include file=$FILEUPLOAD filename=$ATTACHMENTS}',
        ),

Step 6: Create two tpl files for detail-view and edit-view with the following contents:

Path:custom/include/tpls/editview.tpl

<div><input type="file" name="attachments[0]" id="attachments[0]" onclick="file_size(this.id)"/></div>
<div class="input_fields_wrap"><div></div></div><button class="add_field_button">Add More Attachments</button>
{if $fields.id.value != ''}
{foreach from=$filename key=k item=v}
	<br/>
	<a href='#' name={$v.id} id="download_attachment{$k}" class='tabDetailViewDFLink downloadAttachment'>{$v.filename} &nbsp<i class="glyphicon glyphicon-eye-open"></i></a>
	<a href='#' name={$v.id} id="remove_attachment{$k}" class='tabDetailViewDFLink remove_attachment'> &nbsp<i class="glyphicon glyphicon-remove"></i></a>
{/foreach}
{/if}

Path:custom/include/tpls/detailview.tpl

{if $fields.id.value != ''}
<br>
{foreach from=$filename key=k item=v}
   <a href='#' name={$v.id} class='tabDetailViewDFLink downloadAttachment'>{$v.filename} &nbsp<i class="glyphicon glyphicon-eye-open"></i></a><br />
{/foreach}
{/if}

Step 7: As it is SugarCRM’s module, copy modules/<module_name>/views/view.edit.php to custom/modules/<module_name>/views/view.edit.php or edit if it already exists at custom/modules/<module_name>/views/view.edit.php

In function display() add following piece.

public function display(){
		if(!empty($this->bean->id)){
			$this->ss->assign('ATTACHMENTS',$this->getAttachments($this->bean->id,'itb_candidates'));
		}
		$this->ss->assign('FILEUPLOAD','custom/include/tpls/customFileUploadForEditView.tpl');
		parent::display();
	}
function getAttachments($module_id,$module_name){
	global $db;
	if(isset($module_id)){
		$sql = 'SELECT id,filename FROM custom_documents where module_name = "'.$module_name.'" AND module_id = "'.$module_id.'" AND deleted = 0';
		$res = $GLOBALS['db']->query($sql);
		$attachments = array();
		while($row = $db->fetchByAssoc($res)){
			$attachments[] = $row;
		}
		return $attachments;
	}
}

As it is SugarCRM’s module, copy modules/<module_name>/views/view.detail.php to custom/modules/<module_name>/views/view.edit.php or edit if it already exists at custom/modules/<module_name>/views/view. detail .php

public function display(){
		/*
		author - Navin Rakhonde
		desc - download attachments.
		*/
		if(!empty($this->bean->id)){
			$this->ss->assign('ATTACHMENTS',getAttachments($this->bean->id,'itb_candidates'));
		}
		$this->ss->assign('FILEUPLOAD','custom/include/tpls/customFileUploadForDetailView.tpl');
		parent::display();
	}

function getAttachments($module_id,$module_name){
	global $db;
	if(isset($module_id)){
		$sql = 'SELECT id,filename FROM custom_documents where module_name = "'.$module_name.'" AND module_id = "'.$module_id.'" AND deleted = 0';
		$res = $GLOBALS['db']->query($sql);
		$attachments = array();
		while($row = $db->fetchByAssoc($res)){
			$attachments[] = $row;
		}
		return $attachments;
	}
}

Step 8: Create a before save definition logic hook under custom/modules/<module_name>/logic_hooks.php

<?php
$hook_array['before_save'] = Array(); 
$hook_array['before_save'][] = Array(90, 'Attachments', 'custom/modules/<module_name>/uploadAttchments.php','cls_attachments', 'fn_attachments'); 

Add below code in above created file (custom/modules//uploadAttchments.php)

<?php
if (!defined('sugarEntry') || !sugarEntry){
    die('Not A Valid Entry Point');
}

class cls_attachments{
	
    public function fn_attachments(&$bean, $event, $arguments){
		foreach ($_FILES["attachments"]["error"] as $key => $error) {
			if ($error == UPLOAD_ERR_OK) {
				$tmp_name = $_FILES["attachments"]["tmp_name"][$key];
				$name = $_FILES["attachments"]["name"][$key];
				$type = $_FILES["attachments"]["type"][$key];
				fn_upload_attachments($bean->id, $name, $bean->object_name, $type, $tmp_name);
			}
		}
	}
}

function fn_upload_attachments($module_id, $name, $module_name, $type, $tmp_name){
	global $db;
	$uploads_dir = $GLOBALS['sugar_config']['upload_dir'];
	$id = create_guid();
	$sql = " INSERT into itb_documents(id,module_id,filename,module_name,file_mime_type,deleted) VALUES('{$id}','{$module_id}','{$name}','{$module_name}','{$type}','0')";
	$db->query($sql);
	if (!file_exists("$uploads_dir/$module_name")) {
		mkdir("$uploads_dir/$module_name", 0777, true);
	}
	move_uploaded_file($tmp_name, "$uploads_dir/$module_name/$id");
}
?>

Step 9:  create an file in \custom\include\MVC\Controller\entry_point_registry.php and add below code,

<?php

if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

require SUGAR_PATH . '/include/MVC/Controller/entry_point_registry.php';
$entry_point_registry['<entry_point_name>']=array('file' => 'custom/modules/<module_name>/<any_name>.php', 'auth' => true);

?>

Create file custom/modules/<module_name>/<any_name>.php and add below code,

<?php
if (!defined('sugarEntry') || !sugarEntry) {
    die('Not A Valid Entry Point');
}
if($_GET['action_type'] == 'download'){
	$data = json_decode(str_replace(""","\"",array_shift($_POST)), true);
	global $db;
	if(!empty($data)){
		$id = $data['id'];
		$module_name = $data['module'];
		$query = "SELECT filename,file_mime_type FROM custom_documents WHERE id= '$id' and module_name = '$module_name' and deleted=0";
		$result = $db->query($query);
		$row = $db->fetchByAssoc($result);
		$mime_type = $row['file_mime_type'];
		$filename = $row['filename'];
		$file = 'upload/'.$data['folder'].'/'.$data['id'];
		header("Content-Description: File Transfer");
		header("Content-Type: $mime_type");
		header('Content-Disposition: attachment; filename="'.basename($filename).'"');
		header('Expires: 0');
		header('Cache-Control: must-revalidate');
		header('Pragma: public');
		header('Content-Length: ' . filesize($file));
		readfile($file);
	}
}

if($_POST['action_type'] == 'remove'){
	global $db;
	$id = trim($_POST['id']);
	if(!empty($id)){
		$remove_attachments = "update custom_documents set deleted=1 where id ='$id'";
		$result_remove_attachments = $db->query($remove_attachments);
		//unlink('upload/'.$_POST['folder'].'/'.$id);
		$data = array();
		$data['flag'] = true;
		$data['attachment_id'] = $id;
		echo json_encode($data);
	}
	else{
		$data = array();
		$data['flag'] = false;
		echo json_encode($data);
	}
}

Note :

Here, <module_name> means the module name you see in the URL, for example, Contacts, Leads, etc.
Here, <any_name> means the any file name for example, custom_fields, search_fields, etc.
Here, <entry_point_name> means any name as your requirements

Hope you find this blog post helpful.

Feel free to add comments and queries, that helps us to improve the quality of posts.

You can contact us at info@infotechbuddies.com

Thank you.

Spread the love

One thought on “Creating a file field type with multiple upload and download

Leave a Reply

Your email address will not be published. Required fields are marked *