Overview

File objects allow files to be uploaded to a DynaForm and attached to the current case. Clicking on a file object opens a dialog box to upload a file.

Properties

Behavior

Input

Beginning with version 1.2-2467, it is possible to upload Input Documents from inside a DynaForm. Select the Input Document in the Input property, and all uploaded files will be treated as that Input Document.

XML Definition:

<NAME ... input="INPUT-DOCUMENT-UID" ...>...NAME>

File Access and Storage

Uploaded input documents are available for review afterwards by clicking on the STEPS tab under the CASES menu then clicking on [+] next to the name of the Input Document to see the list of documents which have been uploaded thus far in the case. All other uploaded files (which are not Input Documents) are available to the user by clicking on the INFORMATION tab under the CASES menu. In the "Information" sidebar click on the Uploaded Documents button to see all the documents uploaded during the case. (Remember that a user must either be the currently assigned user or have been assigned Process Permissions to be able to access a case.)

Since uploaded files are stored as files in the harddrive, their size can be as large as allowed by the operating system and harddrive format. Nonetheless, PHP by default is configured to allow the upload of files no larger than 2MB. To increase the maximum size of uploaded files, edit your php.ini file to allow for larger file uploads.

Look for the "File Uploads" section and set file_uploads = On and increase the size of upload_max_filesize:

;;;;;;;;;;;;;;;; ; File Uploads ; ;;;;;;;;;;;;;;;; ; Whether to allow HTTP file uploads. file_uploads = On ; Temporary directory for HTTP uploaded files (will use system default if not specified). ;upload_tmp_dir = ; Maximum allowed size for uploaded files. upload_max_filesize = 2M

Uploaded files are passed to the ProcessMaker server as POST data, whose maximum size by default is 8MB. If larger files need to be uploaded, also increase the size of post_max_size:

; Maximum size of POST data that PHP will accept. post_max_size = 8M

Uploaded files are stored in the sites//files// directory. For the default "workflow" workspace, files are generally found at:
Linux/UNIX:

/opt/processmaker/shared/sites/workflow/files/<CASE-UID>/

Windows:
If manually installed or installed in version 1.6-4260 or later:

C:\Program Files\ProcessMaker\processmaker\shared\sites\workflow\files\<CASE-UID>\

If installed before version 1.6-4260:

C:\Program Files\ProcessMaker\apps\processmaker\shared\workflow_data\sites\workflow\files\<CASE-UID>\

Uploaded files retain their extension, but they are saved with the name of their UID, which is a string of 32 hexadecimal numbers to uniquely identify objects in ProcessMaker. For instance a file called "myinvoice.pdf" might get renamed to "6772460934ab6834e131308085665262.pdf".

Accessing Files with JavaScript

To validate the filename which is uploaded to a file field, read its value property. For example, the following JavaScript code ensures that only files which have an extension of .txt, .htm or .html are uploaded to a file field named "reportFile":

getField("reportFile").onchange = function() {
   var re = /(?:\.([^.]+))?$/;
   var ext = re.exec(this.value)[1];
   if (ext == "txt" || ext == "html" || ext == "htm") {
      return true;
   }
   else {
      alert("Error: Only files with an extension of .txt, .html or .htm are allowed!");
      this.value = ""; //reset the file to empty.
      return false;
   }
}

Opening the contents of a file

The File API can be used to open files which have been uploaded to a file field, however, this API is only available Firefox 7+, Internet Explorer 10.0+, Chrome 13+ and Safari 6.0+. For earlier versions of Internet Explorer, ActiveX can be used if it is enabled.

In this example, JavaScript is used to open the contents of a file uploaded to a file field named "reportFile". It searches whether the file contains the string "monthly report". If so, it allows the file to be used. Otherwise, it clears the file field and displays a message telling the user that the file doesn't contain "monthly report" or can't be opened.

getField("upload").onchange = function() {
   if (this.value == "") {
      return true;
   }

   var file = this.files[0];
   //if the File API is supported: Firefox 7+, IE 10+, Chrome 13+, Sarfari 6+
   if (file) {
      var reader = new FileReader();
      reader.readAsText(file, "UTF-8");
      reader.onload = function (evt) {
         var contents = evt.target.result;
         //if case insensitive regex search doesn't find "monthly report":
         if (contents.search(/monthly report/i) == -1) {
            alert("Error: File '" + file.name + "' does not contain 'Monthly report'.");
            getField("reportFile").value != "";
            return false;
         }
         else return true;
      };
      reader.onerror = function (evt) {
         alert("Error reading file '" + file.name + "'");
         getField("reportFile").value = "";
         return false;
      };
   }
   else { // try the IE method
      try {
         var fso  = new ActiveXObject("Scripting.FileSystemObject");
         var fh = fso.OpenTextFile(this.value, 1);
         var contents = fh.ReadAll();
         fh.Close();
         if (contents.search(/monthly report/i) == -1) {
            alert("Error: File '" + this.value + "' does not contain 'Monthly report'.");
            this.value = "";
            return false;
         }
         else return true;
      }
      catch (Exception) {
         alert("Error: Cannot open file '" + this.value + "'.");
         this.value = "";
         return false;
      }
   }
}

Accessing Files with PHP

When a DynaForm is submitted, the case variable created for a file object just contains the filename (not including the path) of the uploaded file. The file is assigned a case-document UID and is stored in the file system under its case UID and its case-document UID and version number at the path:

<INSTALL-DIRECTORY>/shared/sites/<WORKSPACE>/files/<CASE-UID>/<CASE-DOCUMENT-UID>_<VERSION-NUMBER>.<EXT>

The extension of the saved file stays the same as the original file, but the filename is replaced by the unique ID for the case-document. The version number starts counting from the number 1. If a new version of an Input Document file is uploaded, then the version number will increment by 1:

<INSTALL-DIRECTORY>/shared/sites/<WORKSPACE>/files/<CASE-UID>/<CASE-DOCUMENT-UID>_<VERSION-NUMBER>.<EXT>

For example, the file "invoice.pdf" uploaded to case "7981335114dd4055c4db591044609268" (in the default "workflow" workspace on a Linux server) gets assigned a case-document UID of "3431059204de557e849e904027595620". Then, it would be stored at:

/opt/processmaker/shared/sites/workflow/files/7981335114dd4055c4db591044609268/3431059204de557e849e904027595620_1.pdf

In version 2.5.1 and later, an option was added to allow case files to be stored in a series of 3 subdirectories created from the case's unique ID to avoid the 32K file limits of Linux's ext3 file system. In version 3.0 and later, this option is activated by default. This means that a directory using the case's unique ID, such as /7981335114dd4055c4db591044609268/ in the above example, becomes 3 subdirectories whose names are three characters in the case's unique ID, with the rest of the unique ID placed in the name of another subdirectory:

/opt/processmaker/shared/sites/workflow/files/798/133/511/4dd4055c4db591044609268/3431059204de557e849e904027595620_1.pdf

If accessing an attached file in a ProcessMaker trigger or plugin, the it is recommended to use the defined constants PATH_DOCUMENT, which holds the location of the case files, such as "/opt/processmaker/shared/sites/workflow/files/" on a Linux/UNIX system, and PATH_SEP, which is "/" on Linux/UNIX systems and "\" on Windows systems to separate directories in the file system. The G::getPathFromUID() method may be used in version 2.5.1 or later to break a unique ID number into subdirectories in this way if the system requires it. For example to get an attached file with the case-file ID of 6960198894e6927e0a40b14004833875 and the version number 1 in the current case:

$g = new G();
$OutDocPath = PATH_DOCUMENT . $g->getPathFromUID(@@APPLICATION) . PATH_SEP . "6960198894e6927e0a40b14004833875_1.pdf";
If unsure if in version 2.5.1 or later, then use method_exists() to check whether G::getPathFromUID() exists before calling it.
$caseId = 'a03459e1cb9824dac456780eca7b5cf6';
$g = new G();
$caseIdPath = method_exists(G, "getPathFromUID") ? $g->getPathFromUID($caseId) : $caseId;
$OutDocPath = PATH_DOCUMENT . $caseIdPath . PATH_SEP . "6960198894e6927e0a40b14004833875_2.pdf";

Information about uploaded files (including input documents) is stored in the wf_.APP_DOCUMENT table. The APP_DOC_UID field is case-document UID assigned to the uploaded file and the APP_UID field is the case UID. If the uploaded file is an Input Document, then the DOC_UID field will contain the UID for the Input Document and the APP_DOC_TYPE field will be set to 'INPUT'. If uploaded file is not an Input Document, then DOC_UID will be set to '-1' and APP_DOC_TYPE will be set to 'ATTACHED'. The original filename of the uploaded file is stored in the wf_.CONTENT table, where CON_ID is set to the case-document UID, CON_CATEGORY is set to 'APP_DOC_FILENAME', and CON_VALUE is the original filename.

Uploaded files can be opened with a trigger by looking up the file location in the database and then using standard PHP functions to open the file saved to the file system.

Note: Uploaded files are not saved to the file system and records are not created in the database until after any triggers associated with the DynaForm are fired. In order to manipulate uploaded files with a trigger, the trigger should be set to fire before the next step in the task. If the DynaForm is the last step in the task, then set the trigger to fire before assignment of the next user.

Inserting Uploaded Files in an Email

For example, a DynaForm has a file field named "UploadInvoice", which is an HTML file which needs to be sent out in an email to the Accounting department. In the Process Files Manager, a template file has been created in the mailTemplates directory which is named InvoiceTemplate.html. This email template file contains the following:

Invoice from case: @#APPLICATION Date: @#Date Time: @#Time @#Contents

After the DynaForm is submitted, the following trigger is fired before the next step which queries the CONTENT and APP_DOCUMENT tables to find where the file has been stored in the file system. The trigger then opens the file and extracts the contents. If the file is an HTML document, it only extracts the text between the <body>tags. Then, it uses the PMFSendMessage()function to send out a message with the contents of the uploaded file inserted into the InvoiceTemplate.html file.

if (!empty(trim(@@UploadInvoice))) {
   //assign to normal PHP variables, so can be inserted in strings:
   $invoiceFile = @@UploadInvoice;
   $caseId = @@APPLICATION;
   //query to find the assigned UID for the uploaded file:
   $query = "SELECT APP_DOC_UID FROM CONTENT C, APP_DOCUMENT AD " .
      "WHERE C.CON_VALUE = '$invoiceFile' AND " .
      "AD.APP_DOC_UID = C.CON_ID AND AD.APP_UID = '$caseId'";
   $result = executeQuery($query);

   if (is_array($result) and count($result) > 0) {
      $g = new G();
      $caseIdPath = method_exists(G, "getPathFromUID") ? $g->getPathFromUID($caseId) : $caseId;
      $filePath = PATH_DOCUMENT . $caseIdPath . PATH_SEP .
         $result[1]['APP_DOC_UID'] . '_1.' . pathinfo($invoiceFile, PATHINFO_EXTENSION);
      $invoiceContents = file_get_contents($filePath);

      if ($invoiceContents != FALSE) {
         //pull out the text between the <body>...</body> tags of the HTML document
         if (preg_match('/<body>(.*)<\/body>/i', $invoiceContents, $matches)) {
             $invoiceContents = $matches[1]; //Only include the (.*) part
         }
         PMFSendMessage(@@APPLICATION, 'boss@acme.com', 'accounting@acme.com',
            '', '', 'Invoice to review', 'InvoiceTemplate.html',
            array(
               'Date'=> getCurrentDate(),
               'Time' => getCurrentTime(),
               'Contents'=>$invoiceContents
            )
         );
      }
   }
}

Inserting Uploaded Files in a Database

The same code above can be used to insert the uploaded file into a field in a database. Just replace the if section in the code above with the following code, which should work for both text files and binary files:

if ($invoiceContents != FALSE) {
   //the UID for the database connection:
   $db = 'XXXXXXXXXXXXXXXXXXXXXXXX';
   $invoiceContents = addslashes($invoiceContents);
   //insert $invoiceContents in the database:
   executeQuery("INSERT INTO ACCOUNTS (ID, INVOICE) VALUES ('$caseId', '$invoiceContents')", $db);
}

For example, to insert the file into a ProcessMaker Report Table named "casedata", go into the rp_ database and add a BLOB field named "invoice" to the CASE_DATA table. Then, use the following code to insert the uploaded invoice file into the rp_.CASE_DATA.invoice field:

if ($invoiceContents != FALSE) {
   $db = 'report';
   $caseId = @@APPLICATION;
   $invoiceContents = addslashes($invoiceContents);
   //insert $invoiceContents in the database:
   executeQuery("UPDATE CASE_DATA SET invoice='$invoiceContents' WHERE APP_UID = '$caseId'", $db);
}

Importing an Uploaded CSV File into a Grid

To import database or spreadsheet data into a DynaForm grid, export the data as a comma separated value (CSV) file. Then, upload the data to a file field and fire a trigger before the next step to parse the file and insert it into an associative array of associative arrays, which can be assigned to a grid in a subsequent DynaForm.

In this example, there is a file field named "csvFile" where the user uploads a CSV file, whose data will be imported into a subsequent grid named "expensesGrid". It contains four fields named "expDate", "expAccount", "expAmount", "expDescription", which will be populated. If unable to open the uploaded CSV file or can't read its data, then an error message is displayed, and the case is redirected to the previous DynaForm with the unique ID "67172619648d67d3d5db8f8066114860" to upload a new CSV file.

if (!isset(@@csvFile) or empty(@@csvFile)) {
    $mesg = "CSV File is empty. Please upload again.";
    goto uploadAgain;
}
$csvFile = trim(@@csvFile); #assign to PHP variables to insert in query
$caseId = @@APPLICATION;

//find the assigned UID for the uploaded file:
$query = "SELECT C.CON_ID FROM CONTENT C, APP_DOCUMENT AD WHERE
   AD.APP_UID = '$caseId' AND AD.APP_DOC_UID = C.CON_ID AND C.CON_VALUE = '$csvFile'"
;
$result = executeQuery($query);
if (!is_array($result) || count($result) == 0) {
   $mesg = "Error: Unable to find the uploaded file '$csvFile' in the APP_DOCUMENT table.";
   goto uploadAgain;
}
$g = new G();
$caseIdPath = method_exists(G, "getPathFromUID") ? $g->getPathFromUID($caseId) : $caseId;
$filePath = PATH_DOCUMENT . $caseIdPath . PATH_SEP . $result[1]['CON_ID'] . '_1.' .
   pathinfo($csvFile, PATHINFO_EXTENSION);
$aRecords = file($filePath); //Open the CSV file as an array for each line

if ($aRecords === false or !is_array($aRecords) or count($aRecords) < 1) {
   $mesg = "Error: CSV File '$csvFile' is not valid format or does not contain any records.";
   goto uploadAgain;
}
@=expensesGrid = array();

//Assume that the first line of the CSV file is data and not column headers.
//Loop through the array and for each record break the line into its individual fields.
for ($i = 0; $i < count($aRecords); $i++) {
   //change to ',' if using commas instead of semicolons in the CSV File
   $aFields = explode(';', $aRecords[$i]);
   //change the fields names to match your grid:
   @=expensesGrid[$i + 1] = array(
      'expDate'        => trim($aFields[0], "\t\n\r\" "),
      'expAccount'     => trim($aFields[1], "\t\n\r\" "),
      'expAmount'      => trim($aFields[2], "\t\n\r\" "),
      'expDescription' => trim($aFields[3], "\t\n\r\" ")
   );
}
goto endScript;

uploadAgain:
   $g = new G();
   $g->SendMessageText($mesg, 'ERROR');
   //change for the unique ID of your DynaForm:
   PMFRedirectToStep(@@APPLICATION, @%INDEX, 'DYNAFORM', '67172619648d67d3d5db8f8066114860');

endScript: