Document module improvements, take 3.
authorbradymiller <bradymiller@users.sourceforge.net>
Sun, 28 Apr 2013 17:43:35 +0000 (28 10:43 -0700)
committerbradymiller <bradymiller@users.sourceforge.net>
Wed, 1 May 2013 02:10:00 +0000 (30 19:10 -0700)
 -Can now store more than one level of directory structure within the documents directory.
  (this information is stored in the new path_depth field in the documents sql table entry)
 -Incorporated into Rod's pdf report creation in custom report and in the Document module
  CCD/CCR document viewer.
 -Also will use/create random directories (0-10000) when importing a document that is not
  mapped to a patient to avoid all the documents being placed into one directory.
 -Also created some document functions (such as addNewDocument()) in library/documents.php
  to ease importing new documents in API etc.
 -Incorporated addNewDocument() function in phimail direct module.

 NOTE that there is a previous bug noted where documents within the CouchDB server will
 not work in the Document module CCD/CCR viewer. I did not address this bug on this commit.

ccr/display.php
controllers/C_Document.class.php
interface/patient_file/report/custom_report.php
library/classes/Document.class.php
library/direct_message_check.inc
library/documents.php [new file with mode: 0644]
sql/4_1_1-to-4_1_2_upgrade.sql
sql/database.sql
version.php

index 2635c5d..d66827f 100644 (file)
@@ -7,10 +7,17 @@ $document_id = $_GET['doc_id'];
 $d = new Document($document_id);
 $url =  $d->get_url();
 $url = preg_replace("|^(.*)://|","",$url);
+
+// Collect filename and path
 $from_all = explode("/",$url);
 $from_filename = array_pop($from_all);
-$from_patientid = array_pop($from_all);
-$temp_url = $GLOBALS['OE_SITE_DIR'] . '/documents/' . $from_patientid . '/' . $from_filename;
+$from_pathname_array = array();
+for ($i=0;$i<$d->get_path_depth();$i++) {
+  $from_pathname_array[] = array_pop($from_all);
+}
+$from_pathname_array = array_reverse($from_pathname_array);
+$from_pathname = implode("/",$from_pathname_array);
+$temp_url = $GLOBALS['OE_SITE_DIR'] . '/documents/' . $from_pathname . '/' . $from_filename;
 if (!file_exists($temp_url)) {
   echo xl('The requested document is not present at the expected location on the filesystem or there are not sufficient permissions to access it.','','',' ') . $temp_url;
 }else{
@@ -38,4 +45,4 @@ if (!file_exists($temp_url)) {
                echo $html;
   }
 }
-?>
\ No newline at end of file
+?>
index 5032c96..da37285 100644 (file)
@@ -34,7 +34,26 @@ class C_Document extends Controller {
                        $this->file_path = $GLOBALS['OE_SITE_DIR'].'/documents/temp/';
                }
                else{
-               $this->file_path = $this->_config['repository'] . preg_replace("/[^A-Za-z0-9]/","_",$_GET['patient_id']) . "/";
+                        if ( (!empty($_GET['higher_level_path'])) && (is_numeric($_GET['patient_id']) && $_GET['patient_id']>0) ) {
+                                // Allow higher level directory structure in documents directory and a patient is mapped
+                               $this->file_path = $this->_config['repository'] . preg_replace("/[^A-Za-z0-9\/]/","_",$_GET['higher_level_path']) . "/";
+                        }
+                        else if (!empty($_GET['higher_level_path'])) {
+                                // Allow higher level directory structure in documents directory and there is no patient mapping
+                                //  (Since a patient is not mapped, will create up to 10000 random directories and increment the path_depth by 1)
+                                $this->file_path = $this->_config['repository'] . preg_replace("/[^A-Za-z0-9\/]/","_",$_GET['higher_level_path']) . "/" . rand(1,10000)  . "/";
+                                $_POST['path_depth'] = $_POST['path_depth'] + 1;
+                        }
+                        else if ( !(is_numeric($_GET['patient_id'])) || !($_GET['patient_id']>0) ) {
+                                // This is the default action except there is no patient mapping (when patient_id is 00 or direct)
+                                //  (Since a patient is not mapped, will create up to 10000 random directories and set the path_depth to 2)
+                                $this->file_path = $this->_config['repository'] . preg_replace("/[^A-Za-z0-9]/","_",$_GET['patient_id']) . "/" . rand(1,10000)  . "/";
+                                $_POST['path_depth'] = 2;
+                        }
+                        else {
+                                // This is the default action where the patient is is used as one level directory structure in documents directory
+                                $this->file_path = $this->_config['repository'] . preg_replace("/[^A-Za-z0-9]/","_",$_GET['patient_id']) . "/";
+                        }
                }
                $this->_args = array("patient_id" => $_GET['patient_id']);
                
@@ -104,7 +123,7 @@ class C_Document extends Controller {
                                }else{
                                
                                        if (!file_exists($this->file_path)) {
-                                               if (!mkdir($this->file_path,0700)) {
+                                               if (!mkdir($this->file_path,0700,true)) {
                                                        $error .= "The system was unable to create the directory for this upload, '" . $this->file_path . "'.\n";
                                                }
                                        }
@@ -192,10 +211,16 @@ class C_Document extends Controller {
                                        $this->assign("upload_success", "true");
                                        $d = new Document();
                                        $d->storagemethod = $GLOBALS['document_storage_method'];
-                                       if($harddisk == true)
+                                       if($harddisk == true) {
                                                $d->url = "file://" .$this->file_path.$fname;
-                                       else
+                                                if (is_numeric($_POST['path_depth'])) {
+                                                        // this is for when directory structure is more than one level
+                                                        $d->path_depth = $_POST['path_depth'];
+                                                }
+                                        }
+                                       else {
                                                $d->url = $fname;
+                                        }
                                        if($couchDB == true){
                                                $d->couch_docid = $docid;
                                                $d->couch_revid = $revid;
@@ -443,18 +468,27 @@ class C_Document extends Controller {
                //change full path to current webroot.  this is for documents that may have
                //been moved from a different filesystem and the full path in the database
                //is not current.  this is also for documents that may of been moved to
-               //different patients
+               //different patients. Note that the path_depth is used to see how far down
+                //the path to go. For example, originally the path_depth was always 1, which
+                //only allowed things like documents/1/<file>, but now can have more structured
+                //directories. For example a path_depth of 2 can give documents/encounters/1/<file>
+                // etc.
                // NOTE that $from_filename and basename($url) are the same thing
                $from_all = explode("/",$url);
                $from_filename = array_pop($from_all);
-               $from_patientid = array_pop($from_all);
+               $from_pathname_array = array();
+                for ($i=0;$i<$d->get_path_depth();$i++) {
+                        $from_pathname_array[] = array_pop($from_all);
+                }
+                $from_pathname_array = array_reverse($from_pathname_array);
+                $from_pathname = implode("/",$from_pathname_array);
     if($couch_docid && $couch_revid){
        //for couchDB no URL is available in the table, hence using the foreign_id which is patientID
        $temp_url = $GLOBALS['OE_SITE_DIR'] . '/documents/temp/' . $d->get_foreign_id() . '_' . $from_filename;
        
        }
        else{
-       $temp_url = $GLOBALS['OE_SITE_DIR'] . '/documents/' . $from_patientid . '/' . $from_filename;
+       $temp_url = $GLOBALS['OE_SITE_DIR'] . '/documents/' . $from_pathname . '/' . $from_filename;
        }
        
                if (file_exists($temp_url)) {
@@ -505,7 +539,7 @@ class C_Document extends Controller {
                                $url = $GLOBALS['OE_SITE_DIR'] . '/documents/temp/' . $convertedFile;
                                }
                                else{
-                               $url = $GLOBALS['OE_SITE_DIR'] . '/documents/' . $from_patientid . '/' . $convertedFile;
+                               $url = $GLOBALS['OE_SITE_DIR'] . '/documents/' . $from_pathname . '/' . $convertedFile;
                 }
                                header("Pragma: public");
                            header("Expires: 0");
@@ -776,12 +810,21 @@ class C_Document extends Controller {
                 //change full path to current webroot.  this is for documents that may have
                 //been moved from a different filesystem and the full path in the database
                 //is not current.  this is also for documents that may of been moved to
-                //different patients
+                //different patients. Note that the path_depth is used to see how far down
+                //the path to go. For example, originally the path_depth was always 1, which
+                //only allowed things like documents/1/<file>, but now can have more structured
+                //directories. For example a path_depth of 2 can give documents/encounters/1/<file>
+                // etc.
                 // NOTE that $from_filename and basename($url) are the same thing
                 $from_all = explode("/",$url);
                 $from_filename = array_pop($from_all);
-                $from_patientid = array_pop($from_all);
-                $temp_url = $GLOBALS['OE_SITE_DIR'] . '/documents/' . $from_patientid . '/' . $from_filename;
+                $from_pathname_array = array();
+                for ($i=0;$i<$d->get_path_depth();$i++) {
+                        $from_pathname_array[] = array_pop($from_all);
+                }
+                $from_pathname_array = array_reverse($from_pathname_array);
+                $from_pathname = implode("/",$from_pathname_array);
+                $temp_url = $GLOBALS['OE_SITE_DIR'] . '/documents/' . $from_pathname . '/' . $from_filename;
                 if (file_exists($temp_url)) {
                         $url = $temp_url;
                 }
index 87975f3..6bfcd2a 100644 (file)
@@ -758,17 +758,23 @@ foreach ($ar as $key => $val) {
                 if($couch_docid && $couch_revid){
                   $url_file = $d->get_couch_url($pid,$encounter);
                 }
-                // just grab the last two levels, which contain filename and patientid
+                // Collect filename and path
                 $from_all = explode("/",$url_file);
                 $from_filename = array_pop($from_all);
-                $from_patientid = array_pop($from_all);
+                $from_pathname_array = array();
+                for ($i=0;$i<$d->get_path_depth();$i++) {
+                  $from_pathname_array[] = array_pop($from_all);
+                }
+                $from_pathname_array = array_reverse($from_pathname_array);
+                $from_pathname = implode("/",$from_pathname_array);
+
                 if($couch_docid && $couch_revid) {
                   $from_file = $GLOBALS['OE_SITE_DIR'] . '/documents/temp/' . $from_filename;
                   $to_file = substr($from_file, 0, strrpos($from_file, '.')) . '_converted.jpg';
                 }
                 else {
                   $from_file = $GLOBALS["fileroot"] . "/sites/" . $_SESSION['site_id'] .
-                    '/documents/' . $from_patientid . '/' . $from_filename;
+                    '/documents/' . $from_pathname . '/' . $from_filename;
                   $to_file = substr($from_file, 0, strrpos($from_file, '.')) . '_converted.jpg';
                 }
 
index 5bcb9d3..f449df4 100644 (file)
@@ -301,6 +301,12 @@ class Document extends ORDataObject{
        function get_url_path() {
                return dirname(preg_replace("|^(.*)://|","",$this->url)) ."/";
        }
+        function get_path_depth() {
+                return $this->path_depth;
+        }
+        function set_path_depth($path_depth) {
+                $this->path_depth = $path_depth;
+        }
        function set_mimetype($mimetype) {
                $this->mimetype = $mimetype;
        }
index c2a93cc..968b324 100644 (file)
@@ -27,6 +27,7 @@ require_once(dirname(__FILE__) . "/log.inc");
 require_once(dirname(__FILE__) . "/sql.inc");
 require_once(dirname(__FILE__) . "/pnotes.inc");
 require_once(dirname(__FILE__) . "/../controllers/C_Document.class.php");
+require_once(dirname(__FILE__) . "/documents.php");
 
 /**
  * Connect to a phiMail Direct Messaging server and check for any incoming status
@@ -375,27 +376,17 @@ function phimail_service_userID($name='phimail-service') {
  */
 function phimail_store($name,$mime_type,$fn) {
 
-   //upload using existing controller framework
-   $_FILES['file']['name'][0]=$name;
-   $_FILES['file']['type'][0]=$mime_type;
-   $_FILES['file']['tmp_name'][0]=$fn;
-   $_FILES['file']['error'][0]=0;
-   $_FILES['file']['size'][0]=filesize($fn);
-   $_GET['patient_id']='direct'; //new documents get put into in [fileroot]/sites/[site]/documents/direct/
-   $_POST['destination']='';
-   $_POST['submit']='Upload';
-   $_POST['patient_id']='00'; //workaround because '0' becomes a Null and not a zero in documents.foreign_id
-   $_POST['category_id']='1'; //since we don't know what kind of document; these show up in root Documents folder
-   $_POST['process']='true';
-
-   $user = phimail_service_userID();
-   
-   $cd = new C_Document();
-   $cd->upload_action_process($user); 
-   @unlink($fn);
-   $v = $cd->get_template_vars("file");
-   if (!isset($v) || !$v) return false;
-   return array ("doc_id" => $v[0]->id, "url" => $v[0]->url);
+    // Collect phimail user id
+    $user = phimail_service_userID();
+
+    // Import the document
+    $return = addNewDocument($name,$mime_type,$fn,0,filesize($fn),$user,'direct');
+
+    // Remove the temporary file
+    @unlink($fn);
+
+    // Return the result
+    return $return;
 }
 
 /**
diff --git a/library/documents.php b/library/documents.php
new file mode 100644 (file)
index 0000000..2bfd1b5
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Functions for documents.
+ *
+ * Copyright (C) 2013 Brady Miller <brady@sparmy.com>
+ *
+ * LICENSE: This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
+ *
+ * @package OpenEMR
+ * @author  Brady Miller <brady@sparmy.com>
+ * @link    http://www.open-emr.org
+ */
+
+require_once($GLOBALS['fileroot']."/controllers/C_Document.class.php");
+
+/**
+ * Function to add a document via the C_Document class.
+ *
+ * @param  string         $name                            Name of the document
+ * @param  string         $type                            Mime type of file
+ * @param  string         $tmp_name                        Temporary file name
+ * @param  string         $error                           Errors in file upload
+ * @param  string         $size                            Size of file
+ * @param  int            $owner                           Owner/user/service that imported the file
+ * @param  string         $patient_id_or_simple_directory  Patient id or simple directory for storage when patient id not known (such as '00' or 'direct')
+ * @param  int            $category_id                     Document category id
+ * @param  string         $higher_level_path               Can set a higher level path here (and then place the path depth in $path_depth)
+ * @param  int            $path_depth                      Path depth when using the $higher_level_path feature
+ * @return array/boolean                                   Array(doc_id,url) of the file as stored in documents table, false = failure
+ */
+function addNewDocument($name,$type,$tmp_name,$error,$size,$owner='',$patient_id_or_simple_directory="00",$category_id='1',$higher_level_path='',$path_depth='1') {
+
+    if (empty($owner)) {
+      $owner = $_SESSION['authUserID'];
+    }
+
+    // Build the $_FILES array
+    $TEMP_FILES = array();
+    $TEMP_FILES['file']['name'][0]=$name;
+    $TEMP_FILES['file']['type'][0]=$type;
+    $TEMP_FILES['file']['tmp_name'][0]=$tmp_name;
+    $TEMP_FILES['file']['error'][0]=$error;
+    $TEMP_FILES['file']['size'][0]=$size;
+    $_FILES = $TEMP_FILES;
+
+    // Build the parameters
+    $_GET['higher_level_path']=$higher_level_path;
+    $_GET['patient_id']=$patient_id_or_simple_directory;
+    $_POST['destination']='';
+    $_POST['submit']='Upload';
+    $_POST['path_depth']=$path_depth;
+    $_POST['patient_id']=(is_numeric($patient_id_or_simple_directory) && $patient_id_or_simple_directory>0) ? $patient_id_or_simple_directory : "00";
+    $_POST['category_id']=$category_id;
+    $_POST['process']='true';
+
+    // Add the Document and return the newly added document id
+    $cd = new C_Document();
+    $cd->upload_action_process($owner);
+    $v = $cd->get_template_vars("file");
+    if (!isset($v) || !$v) return false;
+    return array ("doc_id" => $v[0]->id, "url" => $v[0]->url); 
+}
+
+/**
+ * Function to return the category id of a category title.
+ *
+ * @param  string  $category_title  category title
+ * @return int/boolean              category id (returns false if the category title does not exist)
+ */
+function document_category_to_id($category_title) {
+  $ret = sqlQuery("SELECT `id` FROM `categories` WHERE `name`=?", array($category_title) );
+  if ($ret['id']) {
+    return $ret['id'];
+  }
+  else {
+    return false;
+  }
+}
+?>
index c06e8b5..a5acb7c 100644 (file)
@@ -442,3 +442,7 @@ ALTER TABLE `immunizations`
   ADD COLUMN `added_erroneously` tinyint(1) NOT NULL DEFAULT '0';
 #EndIf
 
+#IfMissingColumn documents path_depth
+ALTER TABLE `documents` ADD COLUMN `path_depth` TINYINT DEFAULT '1' COMMENT 'Depth of path to use in url to find document. Not applicable for CouchDB.';
+#Endif
+
index a27b78c..1ac5388 100644 (file)
@@ -671,6 +671,7 @@ CREATE TABLE `documents` (
   `couch_docid` VARCHAR(100) DEFAULT NULL,
   `couch_revid` VARCHAR(100) DEFAULT NULL,
   `storagemethod` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '0->Harddisk,1->CouchDB',
+  `path_depth` TINYINT DEFAULT '1' COMMENT 'Depth of path to use in url to find document. Not applicable for CouchDB.',
   PRIMARY KEY  (`id`),
   KEY `revision` (`revision`),
   KEY `foreign_id` (`foreign_id`),
index e4cc975..1437517 100644 (file)
@@ -17,7 +17,7 @@ $v_realpatch = '0';
 // is a database change in the course of development.  It is used
 // internally to determine when a database upgrade is needed.
 //
-$v_database = 94;
+$v_database = 95;
 
 // Access control version identifier, this is to be incremented whenever there
 // is a access control change in the course of development.  It is used