Interim autoloaded library/classes via composer classmap, take 4. (#422)
[openemr.git] / library / classes / Document.class.php
blob3ad24935c6989926eb2a548bd619c240a31c0a07
1 <?php
3 require_once(dirname(__FILE__) . "/../pnotes.inc");
4 require_once(dirname(__FILE__) . "/../gprelations.inc.php");
6 /**
7 * class Document
8 * This class is the logical representation of a physical file on some system somewhere that can be referenced with a URL
9 * of some type. This URL is not necessarily a web url, it could be a file URL or reference to a BLOB in a db.
10 * It is implicit that a document can have other related tables to it at least a one document to many notes which join on a documents
11 * id and categories which do the same.
14 class Document extends ORDataObject{
17 * Database unique identifier
18 * @var id
20 var $id;
23 * DB unique identifier reference to some other table, this is not unique in the document table
24 * @var int
26 var $foreign_id;
29 * Enumerated DB field which is met information about how to use the URL
30 * @var int can also be a the properly enumerated string
32 var $type;
35 * Array mapping of possible for values for the type variable
36 * mapping is array text name to index
37 * @var array
39 var $type_array = array();
42 * Size of the document in bytes if that is available
43 * @var int
45 var $size;
48 * Date the document was first persisted
49 * @var string
51 var $date;
54 * URL which point to the document, may be a file URL, a web URL, a db BLOB URL, or others
55 * @var string
57 var $url;
60 * URL which point to the thumbnail document, may be a file URL, a web URL, a db BLOB URL, or others
61 * @var string
63 var $thumb_url;
66 * Mimetype of the document if available
67 * @var string
69 var $mimetype;
72 * If the document is a multi-page format like tiff and has at least 1 page this will be 1 or greater, if a non-multi-page format this should be null or empty
73 * @var int
75 var $pages;
78 * Foreign key identifier of who initially persisited the document,
79 * potentially ownership could be changed but that would be up to an external non-document object process
80 * @var int
82 var $owner;
85 * Timestamp of the last time the document was changed and persisted, auto maintained by DB, manually change at your own peril
86 * @var int
88 var $revision;
91 * Date (YYYY-MM-DD) logically associated with the document, e.g. when a picture was taken.
92 * @var string
94 var $docdate;
97 * 40-character sha1 hash key of the document from when it was uploaded.
98 * @var string
100 var $hash;
103 * DB identifier reference to the lists table (the related issue), 0 if none.
104 * @var int
106 var $list_id;
108 // For tagging with the encounter
109 var $encounter_id;
110 var $encounter_check;
113 * Whether the file is already imported
114 * @var int
116 var $imported;
119 * Constructor sets all Document attributes to their default value
120 * @param int $id optional existing id of a specific document, if omitted a "blank" document is created
122 function __construct($id = "") {
123 //call the parent constructor so we have a _db to work with
124 parent::__construct();
126 //shore up the most basic ORDataObject bits
127 $this->id = $id;
128 $this->_table = "documents";
130 //load the enum type from the db using the parent helper function, this uses psuedo-class variables so it is really cheap
131 $this->type_array = $this->_load_enum("type");
133 $this->type = $this->type_array[0];
134 $this->size = 0;
135 $this->date = date("Y-m-d H:i:s");
136 $this->url = "";
137 $this->mimetype = "";
138 $this->docdate = date("Y-m-d");
139 $this->hash = "";
140 $this->list_id = 0;
141 $this->encounter_id = 0;
142 $this->encounter_check = "";
144 if ($id != "") {
145 $this->populate();
150 * Convenience function to get an array of many document objects
151 * For really large numbers of documents there is a way more efficient way to do this by overwriting the populate method
152 * @param int $foreign_id optional id use to limit array on to a specific relation, otherwise every document object is returned
154 function documents_factory($foreign_id = "") {
155 $documents = array();
157 if (empty($foreign_id)) {
158 $foreign_id= "like '%'";
160 else {
161 $foreign_id= " = '" . add_escape_custom(strval($foreign_id)) . "'";
164 $d = new Document();
165 $sql = "SELECT id FROM " . $d->_table . " WHERE foreign_id " .$foreign_id ;
166 $result = $d->_db->Execute($sql);
168 while ($result && !$result->EOF) {
169 $documents[] = new Document($result->fields['id']);
170 $result->MoveNext();
173 return $documents;
177 * Convenience function to get a document object from a url
178 * Checks to see if there is an existing document with that URL and if so returns that object, otherwise
179 * creates a new one, persists it and returns it
180 * @param string $url
181 * @return object new or existing document object with the specified URL
183 function document_factory_url($url) {
184 $d = new Document();
185 //strip url handler, for now we always assume file://
186 $filename = preg_replace("|^(.*)://|","",$url);
188 if (!file_exists($filename)) {
189 die("An invalid URL was specified to crete a new document, this would only be caused if files are being deleted as you are working through the queue. '$filename'\n");
192 $sql = "SELECT id FROM " . $d->_table . " WHERE url= '" . add_escape_custom($url) ."'" ;
193 $result = $d->_db->Execute($sql);
195 if ($result && !$result->EOF) {
196 if (file_exists($filename)) {
197 $d = new Document($result->fields['id']);
199 else {
200 $sql = "DELETE FROM " . $d->_table . " WHERE id= '" . $result->fields['id'] ."'";
201 $result = $d->_db->Execute($sql);
202 echo("There is a database for the file but it no longer exists on the file system. Its document entry has been deleted. '$filename'\n");
205 else {
206 $file_command = $GLOBALS['oer_config']['document']['file_command_path'] ;
207 $cmd_args = "-i ".escapeshellarg($new_path.$fname);
209 $command = $file_command." ".$cmd_args;
210 $mimetype = exec($command);
211 $mime_array = explode(":", $mimetype);
212 $mimetype = $mime_array[1];
213 $d->set_mimetype($mimetype);
214 $d->url = $url;
215 $d->size = filesize($filename);
216 $d->type = $d->type_array['file_url'];
217 $d->persist();
218 $d->populate();
221 return $d;
225 * Convenience function to generate string debug data about the object
227 function toString($html = false) {
228 $string .= "\n"
229 . "ID: " . $this->id."\n"
230 . "FID: " . $this->foreign_id."\n"
231 . "type: " . $this->type . "\n"
232 . "type_array: " . print_r($this->type_array,true) . "\n"
233 . "size: " . $this->size . "\n"
234 . "date: " . $this->date . "\n"
235 . "url: " . $this->url . "\n"
236 . "mimetype: " . $this->mimetype . "\n"
237 . "pages: " . $this->pages . "\n"
238 . "owner: " . $this->owner . "\n"
239 . "revision: " . $this->revision . "\n"
240 . "docdate: " . $this->docdate . "\n"
241 . "hash: " . $this->hash . "\n"
242 . "list_id: " . $this->list_id . "\n"
243 . "encounter_id: " . $this->encounter_id . "\n"
244 . "encounter_check: " . $this->encounter_check . "\n";
246 if ($html) {
247 return nl2br($string);
249 else {
250 return $string;
254 /**#@+
255 * Getter/Setter methods used by reflection to affect object in persist/poulate operations
256 * @param mixed new value for given attribute
258 function set_id($id) {
259 $this->id = $id;
261 function get_id() {
262 return $this->id;
264 function set_foreign_id($fid) {
265 $this->foreign_id = $fid;
267 function get_foreign_id() {
268 return $this->foreign_id;
270 function set_type($type) {
271 $this->type = $type;
273 function get_type() {
274 return $this->type;
276 function set_size($size) {
277 $this->size = $size;
279 function get_size() {
280 return $this->size;
282 function set_date($date) {
283 $this->date = $date;
285 function get_date() {
286 return $this->date;
288 function set_hash($hash) {
289 $this->hash = $hash;
291 function get_hash() {
292 return $this->hash;
294 function set_url($url) {
295 $this->url = $url;
297 function get_url() {
298 return $this->url;
300 function set_thumb_url($url) {
301 $this->thumb_url = $url;
303 function get_thumb_url() {
304 return $this->thumb_url;
307 * this returns the url stripped down to basename
309 function get_url_web() {
310 return basename_international($this->url);
313 * get the url without the protocol handler
315 function get_url_filepath() {
316 return preg_replace("|^(.*)://|","",$this->url);
319 * get the url filename only
321 function get_url_file() {
322 return basename_international(preg_replace("|^(.*)://|","",$this->url));
325 * get the url path only
327 function get_url_path() {
328 return dirname(preg_replace("|^(.*)://|","",$this->url)) ."/";
330 function get_path_depth() {
331 return $this->path_depth;
333 function set_path_depth($path_depth) {
334 $this->path_depth = $path_depth;
336 function set_mimetype($mimetype) {
337 $this->mimetype = $mimetype;
339 function get_mimetype() {
340 return $this->mimetype;
342 function set_pages($pages) {
343 $this->pages = $pages;
345 function get_pages() {
346 return $this->pages;
348 function set_owner($owner) {
349 $this->owner = $owner;
351 function get_owner() {
352 return $this->owner;
355 * No getter for revision because it is updated automatically by the DB.
357 function set_revision($revision) {
358 $this->revision = $revision;
360 function set_docdate($docdate) {
361 $this->docdate = $docdate;
363 function get_docdate() {
364 return $this->docdate;
366 function set_list_id($list_id) {
367 $this->list_id = $list_id;
369 function get_list_id() {
370 return $this->list_id;
372 function set_encounter_id($encounter_id) {
373 $this->encounter_id = $encounter_id;
375 function get_encounter_id() {
376 return $this->encounter_id;
378 function set_encounter_check($encounter_check) {
379 $this->encounter_check = $encounter_check;
381 function get_encounter_check() {
382 return $this->encounter_check;
385 function get_ccr_type($doc_id){
386 $type = sqlQuery("SELECT c.name FROM categories AS c LEFT JOIN categories_to_documents AS ctd ON c.id = ctd.category_id WHERE ctd.document_id = ?",array($doc_id));
387 return $type['name'];
389 function set_imported($imported) {
390 $this->imported = $imported;
392 function get_imported() {
393 return $this->imported;
395 function update_imported($doc_id) {
396 sqlQuery("UPDATE documents SET imported = 1 WHERE id = ?",array($doc_id));
399 * Overridden function to stor current object state in the db.
400 * current overide is to allow for a just in time foreign id, often this is needed
401 * when the object is never directly exposed and is handled as part of a larger
402 * object hierarchy.
403 * @param int $fid foreign id that should be used so that this document can be related (joined) on it later
406 function persist($fid ="") {
407 if (!empty($fid)) {
408 $this->foreign_id = $fid;
410 parent::persist();
413 function set_storagemethod($str) {
414 $this->storagemethod = $str;
417 function get_storagemethod() {
418 return $this->storagemethod;
421 function set_couch_docid($str) {
422 $this->couch_docid = $str;
425 function get_couch_docid() {
426 return $this->couch_docid;
429 function set_couch_revid($str) {
430 $this->couch_revid = $str;
433 function get_couch_revid() {
434 return $this->couch_revid;
437 function get_couch_url($pid,$encounter){
438 $couch_docid = $this->get_couch_docid();
439 $couch_url = $this->get_url();
440 $couch = new CouchDB();
441 $data = array($GLOBALS['couchdb_dbase'],$couch_docid,$pid,$encounter);
442 $resp = $couch->retrieve_doc($data);
443 $content = $resp->data;
444 $temp_url=$couch_url;
445 $temp_url = $GLOBALS['OE_SITE_DIR'] . '/documents/temp/' . $pid . '_' . $couch_url;
446 $f_CDB = fopen($temp_url,'w');
447 fwrite($f_CDB,base64_decode($content));
448 fclose($f_CDB);
449 return $temp_url;
452 // Function added by Rod to change the patient associated with a document.
453 // This just moves some code that used to be in C_Document.class.php,
454 // changing it as little as possible since I'm not set up to test it.
456 function change_patient($new_patient_id) {
457 $couch_docid = $this->get_couch_docid();
458 $couch_revid = $this->get_couch_revid();
460 // Set the new patient in CouchDB.
461 if ($couch_docid && $couch_revid) {
462 $couch = new CouchDB();
463 $db = $GLOBALS['couchdb_dbase'];
464 $data = array($db, $couch_docid);
465 $couchresp = $couch->retrieve_doc($data);
466 // CouchDB doesnot support updating a single value in a document.
467 // Have to retrieve the entire document, update the necessary value and save again
468 list ($db, $docid, $revid, $patient_id, $encounter, $type, $json) = $data;
469 $data = array($db, $couch_docid, $couch_revid, $new_patient_id, $couchresp->encounter,
470 $couchresp->mimetype, json_encode($couchresp->data), json_encode($couchresp->th_data));
471 $resp = $couch->update_doc($data);
472 // Sometimes the response from CouchDB is not available, still it would
473 // have saved in the DB. Hence check one more time.
474 if(!$resp->_id || !$resp->_rev){
475 $data = array($db, $couch_docid, $new_patient_id, $couchresp->encounter);
476 $resp = $couch->retrieve_doc($data);
478 if($resp->_rev == $couch_revid) {
479 return false;
481 else {
482 $this->set_couch_revid($resp->_rev);
486 // Set the new patient in mysql.
487 $this->set_foreign_id($new_patient_id);
488 $this->persist();
490 // Return true for success.
491 return true;
495 * Create a new document and store its data.
496 * This is a mix of new code and code moved from C_Document.class.php.
498 * @param string $patient_id Patient pid; if not known then this may be a simple directory name
499 * @param integer $category_id The desired document category ID
500 * @param string $filename Desired filename, may be modified for uniqueness
501 * @param string $mimetype MIME type
502 * @param string &$data The actual data to store (not encoded)
503 * @param string $higher_level_path Optional subdirectory within the local document repository
504 * @param string $path_depth Number of directory levels in $higher_level_path, if specified
505 * @param integer $owner Owner/user/service that is requesting this action
506 * @param string $tmpfile The tmp location of file (require for thumbnail generator)
507 * @return string Empty string if success, otherwise error message text
509 function createDocument($patient_id, $category_id, $filename, $mimetype, &$data,
510 $higher_level_path='', $path_depth=1, $owner=0, $tmpfile = null) {
511 // The original code used the encounter ID but never set it to anything.
512 // That was probably a mistake, but we reference it here for documentation
513 // and leave it empty. Logically, documents are not tied to encounters.
515 if($GLOBALS['generate_doc_thumb']) {
517 $thumb_size = ($GLOBALS['thumb_doc_max_size'] > 0) ? $GLOBALS['thumb_doc_max_size'] : null;
518 $thumbnail_class = new Thumbnail($thumb_size);
520 if(!is_null($tmpfile)) {
521 $has_thumbnail = $thumbnail_class->file_support_thumbnail($tmpfile);
522 } else {
523 $has_thumbnail = false;
526 if ($has_thumbnail) {
527 $thumbnail_resource = $thumbnail_class->create_thumbnail(null, $data);
528 if ($thumbnail_resource) {
529 $thumbnail_data = $thumbnail_class->get_string_file($thumbnail_resource);
530 } else {
531 $has_thumbnail = false;
534 } else {
535 $has_thumbnail = false;
538 $encounter_id = '';
539 $this->storagemethod = $GLOBALS['document_storage_method'];
540 $this->mimetype = $mimetype;
541 if ($this->storagemethod == 1) {
542 // Store it using CouchDB.
543 $couch = new CouchDB();
544 $docname = $_SESSION['authId'] . $filename . $patient_id . $encounter_id . date("%Y-%m-%d H:i:s");
545 $docid = $couch->stringToId($docname);
546 $json = json_encode(base64_encode($data));
547 if ($has_thumbnail) {
548 $th_json = json_encode(base64_encode($thumbnail_data));
549 $this->thumb_url = $this->get_thumb_name($filename);
550 } else {
551 $th_json = false;
553 $db = $GLOBALS['couchdb_dbase'];
554 $couchdata = array($db, $docid, $patient_id, $encounter_id, $mimetype, $json, $th_json);
555 $resp = $couch->check_saveDOC($couchdata);
556 if(!$resp->id || !$resp->_rev) {
557 // Not sure what this is supposed to do. The references to id, rev,
558 // _id and _rev seem pretty weird.
559 $couchdata = array($db, $docid, $patient_id, $encounter_id);
560 $resp = $couch->retrieve_doc($couchdata);
561 $docid = $resp->_id;
562 $revid = $resp->_rev;
564 else {
565 $docid = $resp->id;
566 $revid = $resp->rev;
568 if(!$docid && !$revid) {
569 return xl('CouchDB save failed');
571 $this->url = $filename;
572 $this->couch_docid = $docid;
573 $this->couch_revid = $revid;
575 else {
576 // Storing document files locally.
577 $repository = $GLOBALS['oer_config']['documents']['repository'];
578 $higher_level_path = preg_replace("/[^A-Za-z0-9\/]/", "_", $higher_level_path);
579 if ((!empty($higher_level_path)) && (is_numeric($patient_id) && $patient_id > 0)) {
580 // Allow higher level directory structure in documents directory and a patient is mapped.
581 $filepath = $repository . $higher_level_path . "/";
583 else if (!empty($higher_level_path)) {
584 // Allow higher level directory structure in documents directory and there is no patient mapping
585 // (will create up to 10000 random directories and increment the path_depth by 1).
586 $filepath = $repository . $higher_level_path . '/' . rand(1,10000) . '/';
587 ++$path_depth;
589 else if (!(is_numeric($patient_id)) || !($patient_id > 0)) {
590 // This is the default action except there is no patient mapping (when patient_id is 00 or direct)
591 // (will create up to 10000 random directories and set the path_depth to 2).
592 $filepath = $repository . $patient_id . '/' . rand(1,10000) . '/';
593 $path_depth = 2;
594 $patient_id = 0;
596 else {
597 // This is the default action where the patient is used as one level directory structure in documents directory.
598 $filepath = $repository . $patient_id . '/';
599 $path_depth = 1;
601 if (!file_exists($filepath)) {
602 if (!mkdir($filepath, 0700, true)) {
603 return xl('Unable to create patient document subdirectory');
606 // Filename modification to force valid characters and uniqueness.
607 $filename = preg_replace("/[^a-zA-Z0-9_.]/", "_", $filename);
608 $fnsuffix = 0;
609 $fn1 = $filename;
610 $fn2 = '';
611 $fn3 = '';
612 $dotpos = strrpos($filename, '.');
613 if ($dotpos !== FALSE) {
614 $fn1 = substr($filename, 0, $dotpos);
615 $fn2 = '.';
616 $fn3 = substr($filename, $dotpos + 1);
618 while (file_exists($filepath . $filename)) {
619 if (++$fnsuffix > 10000) return xl('Failed to compute a unique filename');
620 $filename = $fn1 . '_' . $fnsuffix . $fn2 . $fn3;
622 $this->url = "file://" . $filepath . $filename;
623 if (is_numeric($path_depth)) {
624 // this is for when directory structure is more than one level
625 $this->path_depth = $path_depth;
627 // Store the file into its proper directory.
628 if (file_put_contents($filepath . $filename, $data) === FALSE) {
629 return xl('Failed to create') . " $filepath$filename";
631 if( $has_thumbnail ) {
632 $this->thumb_url = "file://" . $filepath . $this->get_thumb_name($filename);
633 // Store the file into its proper directory.
634 if (file_put_contents($filepath . $this->get_thumb_name($filename), $thumbnail_data) === FALSE) {
635 return xl('Failed to create') . $filepath . $this->get_thumb_name($filename);
640 $this->size = strlen($data);
641 $this->hash = sha1($data);
642 $this->type = $this->type_array['file_url'];
643 $this->owner = $owner ? $owner : $_SESSION['authUserID'];
644 $this->set_foreign_id($patient_id);
645 $this->persist();
646 $this->populate();
647 if (is_numeric($this->get_id()) && is_numeric($category_id)){
648 $sql = "REPLACE INTO categories_to_documents set " .
649 "category_id = '$category_id', " .
650 "document_id = '" . $this->get_id() . "'";
651 $this->_db->Execute($sql);
653 return '';
657 * Return file name for thumbnail (adding 'th_')
659 function get_thumb_name($file_name) {
660 return 'th_' . $file_name;
664 * Post a patient note that is linked to this document.
666 * @param string $provider Login name of the provider to receive this note.
667 * @param integer $category_id The desired document category ID
668 * @param string $message Any desired message text for the note.
670 function postPatientNote($provider, $category_id, $message='') {
671 // Build note text in a way that identifies the new document.
672 // See pnotes_full.php which uses this to auto-display the document.
673 $note = $this->get_url_file();
674 for ($tmp = $category_id; $tmp;) {
675 $catrow = sqlQuery("SELECT name, parent FROM categories WHERE id = ?", array($tmp));
676 $note = $catrow['name'] . "/$note";
677 $tmp = $catrow['parent'];
679 $note = "New scanned document " . $this->get_id() . ": $note";
680 if ($message) $note .= "\n" . $message;
681 $noteid = addPnote($this->get_foreign_id(), $note, 0, '1', 'New Document', $provider);
682 // Link the new note to the document.
683 setGpRelation(1, $this->get_id(), 6, $noteid);
686 } // end of Document