You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
12 KiB
257 lines
12 KiB
<?php |
|
|
|
/** |
|
* linked_documents.php is a sub section template for the messages.php script. It is primarily used for displaying attached |
|
* documents (usually received from the phimail-server process. It handles the preview and display of documents as well |
|
* as validation of CDA types of documents if direct mail is turned on in the globals. validation is an async process |
|
* that will hit the server and validate the xsd and schematron of the CDA. The errors are then populated and displayed |
|
* on the screen with associated error counts being displayed if they are available. Each document is handled separately |
|
* as an async validation process. |
|
* |
|
* @package openemr |
|
* @link http://www.open-emr.org |
|
* @author Stephen Nielson <snielson@discoverandchange.com> |
|
* @copyright Copyright (c) 2022 Discover and Change, Inc. <snielson@discoverandchange.com> |
|
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 |
|
*/ |
|
|
|
use OpenEMR\Common\Csrf\CsrfUtils; |
|
use OpenEMR\Common\Database\QueryUtils; |
|
use OpenEMR\Common\Twig\TwigContainer; |
|
use OpenEMR\Common\Logging\SystemLogger; |
|
use OpenEMR\Services\Cda\CdaValidateDocumentObject; |
|
|
|
if (empty($noteid)) { |
|
$twig = new TwigContainer(null, $GLOBALS['kernel']); |
|
echo $twig->render('core/unauthorized.html.twig', ['pageTitle' => xl("Linked Documents")]); |
|
exit; |
|
} |
|
|
|
// Get the related document IDs if any. |
|
$linkedDocsSql = "SELECT id1 FROM gprelations WHERE " . |
|
"type1 = ? AND type2 = ? AND id2 = ?"; |
|
$tmp = QueryUtils::fetchRecords($linkedDocsSql, ['1','6',$noteid]); |
|
if (empty($tmp)) { |
|
return; |
|
} |
|
|
|
$enc_list = []; |
|
if (!empty($prow)) { |
|
$results = QueryUtils::fetchRecords("SELECT fe.encounter,fe.date,openemr_postcalendar_categories.pc_catname FROM form_encounter AS fe " . |
|
" LEFT JOIN openemr_postcalendar_categories ON fe.pc_catid=openemr_postcalendar_categories.pc_catid WHERE fe.pid = ? ORDER BY fe.date DESC", array($prow['pid'])); |
|
foreach ($enc_list as $row) { |
|
$enc_list[] = [ |
|
'encounter' => $row['encounter'], |
|
'pc_catname' => xl_appt_category($row['pc_catname']), |
|
'date' => oeFormatShortDate(date("Y-m-d", strtotime($row['date']))) |
|
]; |
|
} |
|
} |
|
|
|
// if we have phimail enabled, we are going to find out if our document is an xml file, if so, we are going to do a validation |
|
// report on the document. This would be a great thing for caching if we wanted to do that, we'll just compute on the fly for now. |
|
$records = []; |
|
$prow = $prow ?? null; |
|
$cdaDocumentValidator = new CdaValidateDocumentObject(); |
|
foreach ($tmp as $record) { |
|
$d = new Document($record['id1']); |
|
$docInformation = [ |
|
'documentId' => $d->get_id(), |
|
'title' => $d->get_name() . "-" . $d->get_id(), |
|
'isCda' => false, |
|
'hasPatient' => false, |
|
'requiresValidation' => false |
|
]; |
|
if (!empty($prow)) { |
|
$docInformation['hasPatient'] = true; |
|
$docInformation['pname'] = $prow['fname'] . " " . $prow['lname']; |
|
$docInformation['pid'] = $prow['pid']; |
|
$docInformation['pubpid'] = $prow['pubpid']; |
|
$docInformation['DOB'] = $prow['DOB']; |
|
} |
|
if ($cdaDocumentValidator->isCdaDocument($d)) { |
|
$docInformation['isCda'] = true; |
|
$docInformation['requiresValidation'] = true; |
|
} |
|
$records[] = $docInformation; |
|
} |
|
|
|
try { |
|
foreach ($records as $record) : ?> |
|
<div class="row mt-2 mb-2 messages-document-row <?php echo $record['requiresValidation'] ? "messages-document-validate" : ""; ?>" |
|
data-doc="<?php echo attr($record['documentId']); ?>"> |
|
<div class="col-12"> |
|
<span class='font-weight-bold'><?php echo xlt('Linked document'); ?>:</span> |
|
<?php if (!$record['hasPatient']) : ?> |
|
<a class='messages-document-link<?php echo $record['requiresValidation'] ? " d-none" : ""; ?>' |
|
href='javascript:void(0);' onClick='previewDocument(<?php echo attr_js($record['documentId']); ?>);' |
|
|
|
> |
|
<?php echo text($record['title']); ?> |
|
</a> |
|
<?php else : ?> |
|
<a class='messages-document-link <?php echo $record['requiresValidation'] ? "d-none" : ""; ?>' |
|
href='javascript:void(0);' |
|
onClick="gotoReport(<?php echo attr_js($record['documentId']); ?>, <?php echo attr_js($record['pname']); ?>, <?php echo attr_js($record['pid']); ?>,<?php echo attr_js($record['pubpid'] ?? $record['pid']); ?>,<?php echo attr_js($record['DOB']); ?>);"> |
|
<?php echo text($record['title']); ?> |
|
</a> |
|
<?php endif; ?> |
|
<?php if ($record['requiresValidation']) : ?> |
|
<span class="validation-line"> |
|
<?php echo text($record['title']); ?> |
|
<span class="text-info"><?php echo xlt("Validating document for errors"); ?>...</span> |
|
<span class="spinner-border-sm spinner-border" role="status"> |
|
<span class="sr-only"><?php echo xlt("Validating document for errors"); ?>...</span> |
|
</span> |
|
</span> |
|
<span class="validation-failed text-danger d-none"> |
|
<?php echo text($record['title']); ?> - |
|
<?php echo xlt("Failed to retrieve validation results from server"); ?> |
|
</span> |
|
<span class="validation-totals validation-totals-success badge badge-pill badge-success p-2 d-none"> |
|
<?php echo xlt("Errors"); ?> <span class="validation-totals-count">0</span> |
|
</span> |
|
<span class="validation-totals validation-totals-failed badge badge-pill badge-danger p-2 d-none"> |
|
<?php echo xlt("Errors"); ?> <span class="validation-totals-count">0</span> |
|
</span> |
|
<?php endif; ?> |
|
<?php if ($record['isCda']) : ?> |
|
<details class="validation-report-errors d-none"> |
|
<summary><?php echo xlt("Validation report"); ?></summary> |
|
<div class="validation-report-errors-container"> |
|
</div> |
|
<a href="javascript:void(0)" onClick='previewCCDADocument(event,<?php echo attr_js($record['documentId']);?>);' |
|
><?php echo xlt("View Processed Document"); ?></a> |
|
</details> |
|
<?php endif; ?> |
|
</div> |
|
</div> |
|
<?php endforeach; |
|
} catch (Exception $exception) { |
|
// if twig throws any exceptions we want to log it. |
|
(new SystemLogger())->errorLogCaller($exception->getMessage(), ['trace' => $exception->getTraceAsString()]); |
|
} |
|
?> |
|
|
|
<script> |
|
|
|
function previewCCDADocument(event, documentId) { |
|
event.preventDefault(); |
|
let url = "<?php echo $GLOBALS['webroot']; ?>" + "/interface/modules/zend_modules/public/encountermanager/previewDocument?docId=" + documentId; |
|
try { |
|
window.open(url); |
|
} |
|
catch (error) |
|
{ |
|
console.error(error); |
|
alert(window.xl("Failed to preview document")); |
|
} |
|
} |
|
function startDocumentValidation() { |
|
top.restoreSession(); |
|
// document validation can take a long time especially if it is happening externally... |
|
// we are going to do an async process to go out and validate the document before we allow the document to be |
|
// loaded. |
|
let records = document.querySelectorAll(".messages-document-validate"); |
|
records.forEach(function(validateRecord) { |
|
|
|
// now we need to make an ajax async request to the server with the document id |
|
let docId = validateRecord.dataset['doc']; |
|
let url = "<?php echo $GLOBALS['webroot'] . "/library/ajax/messages/validate_messages_document_ajax.php?csrf=\" + " . js_url(CsrfUtils::collectCsrfToken()); ?> |
|
|
|
window.fetch(url + "&doc=" + encodeURIComponent(docId) ) |
|
.then(function(result) { |
|
if (!result.ok) { |
|
throw new Error("Failed to get valid response from server"); |
|
} |
|
return result.text(); |
|
}) |
|
.then(function (resultHtml) { |
|
validateRecord.querySelector(".messages-document-link").classList.remove("d-none"); |
|
validateRecord.querySelector(".validation-report-errors-container").innerHTML = resultHtml; |
|
validateRecord.querySelector(".validation-report-errors").classList.remove("d-none"); |
|
validateRecord.querySelector(".validation-line").classList.add("d-none"); |
|
|
|
// cda-validate-results comes from the html, still need to double check in case its not there |
|
let node = validateRecord.querySelector(".cda-validate-results[data-error-count]"); |
|
if (!node) { |
|
console.error("Failed to find node with .cda-validate-results[data-error-count] to populate error count"); |
|
return; |
|
} |
|
let count = +(node.dataset['errorCount'] || 0); |
|
if (isNaN(count)) { |
|
console.error("node with .cda-validate-results[data-error-count] had error count that was not a number"); |
|
return; |
|
} |
|
|
|
let totalsNode; |
|
if (count > 0) { |
|
totalsNode = validateRecord.querySelector(".validation-totals-failed > .validation-totals-count"); |
|
} else { |
|
totalsNode = validateRecord.querySelector(".validation-totals-success > .validation-totals-count"); |
|
} |
|
totalsNode.innerText = count; |
|
totalsNode.parentNode.classList.remove("d-none"); |
|
}) |
|
.catch(function(error) { |
|
console.error(error); |
|
validateRecord.querySelector(".validation-line").classList.add("d-none"); |
|
validateRecord.querySelector(".validation-failed").classList.remove("d-none"); |
|
}); |
|
}); |
|
} |
|
|
|
window.addEventListener("DOMContentLoaded", startDocumentValidation); |
|
|
|
// this originaly came from the messages class... but it makes no sense to have it there when we only use it here. |
|
function gotoReport(doc_id, pname, pid, pubpid, str_dob) { |
|
EncounterDateArray = []; |
|
CalendarCategoryArray = []; |
|
EncounterIdArray = []; |
|
Count = 0; |
|
<?php |
|
if (!empty($enc_list)) { |
|
foreach ($row as $enc_list) { |
|
?> |
|
EncounterIdArray[Count] = '<?php echo attr($row['encounter']); ?>'; |
|
EncounterDateArray[Count] = '<?php echo attr($row['date']); ?>'; |
|
CalendarCategoryArray[Count] = '<?php echo attr($row['pc_catname']); ?>'; |
|
Count++; |
|
<?php |
|
} |
|
} |
|
?> |
|
top.restoreSession(); |
|
$.ajax({ |
|
type: 'get', |
|
url: '<?php echo $GLOBALS['webroot'] . "/library/ajax/set_pt.php";?>', |
|
data: { |
|
set_pid: pid, |
|
csrf_token_form: <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?> |
|
}, |
|
async: false |
|
}); |
|
parent.left_nav.setPatient(pname, pid, pubpid, '', str_dob); |
|
parent.left_nav.setPatientEncounter(EncounterIdArray, EncounterDateArray, CalendarCategoryArray); |
|
var docurl = '../controller.php?document&view' + "&patient_id=" + encodeURIComponent(pid) + "&document_id=" + encodeURIComponent(doc_id) + "&"; |
|
var paturl = 'patient_file/summary/demographics.php?pid=' + encodeURIComponent(pid); |
|
parent.left_nav.loadFrame('dem1', 'pat', paturl); |
|
parent.left_nav.loadFrame('doc0', 'enc', docurl); |
|
top.activateTabByName('enc', true); |
|
} |
|
|
|
|
|
/** |
|
* Given a document that we don't know what patient to attach the document to we need to look at a preview of |
|
* the document. This loads up the document from the Miscellaneous -> New Documents page. To access the page |
|
* the user must have the following ACLs: "patients","docs","write","addonly" |
|
* @param doc_id The id of the document we want to preview in OpenEMR |
|
*/ |
|
function previewDocument(doc_id) { |
|
top.restoreSession(); |
|
var docurl = '../controller.php?document&view' + "&patient_id=0&document_id=" + encodeURIComponent(doc_id) + "&"; |
|
parent.left_nav.loadFrame('adm0', 'msc', docurl); |
|
} |
|
|
|
|
|
</script>
|
|
|