7 * @link https://www.open-emr.org
8 * @author Jerry Padgett <sjpadgett@gmail.com>
9 * @copyright Copyright (c) 2016-2021 Jerry Padgett <sjpadgett@gmail.com>
10 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
13 require_once("../interface/globals.php");
15 use OpenEMR\Common\Acl\AclMain
;
16 use OpenEMR\Common\Csrf\CsrfUtils
;
17 use OpenEMR\Core\Header
;
18 use OpenEMR\Services\DocumentTemplates\DocumentTemplateService
;
20 if (!(isset($GLOBALS['portal_onsite_two_enable'])) ||
!($GLOBALS['portal_onsite_two_enable'])) {
21 echo xlt('Patient Portal is turned off');
25 $authUploadTemplates = AclMain
::aclCheckCore('admin', 'forms');
27 $templateService = new DocumentTemplateService();
29 $patient = json_decode($_POST['upload_pid'] ??
'');
31 $template_content = null;
33 if (($_POST['mode'] ??
null) === 'save_profiles') {
34 $profiles = json_decode($_POST['profiles'], true);
35 $rtn = $templateService->saveAllProfileTemplates($profiles);
37 echo xlt("Profiles successfully saved.");
39 echo xlt('Error! Profiles save failed. Check your Profile lists.');
44 if (($_REQUEST['mode'] ??
null) === 'render_profile') {
45 echo renderProfileHtml();
49 if (($_REQUEST['mode'] ??
null) === 'getPdf') {
50 if ($_REQUEST['docid']) {
51 $template = $templateService->fetchTemplate($_REQUEST['docid']);
52 echo "data:application/pdf;base64," . base64_encode($template['template_content']);
55 die(xlt('Invalid File'));
58 if ($_POST['mode'] === 'get') {
59 if ($_REQUEST['docid']) {
60 $template = $templateService->fetchTemplate($_POST['docid']);
61 echo $template['template_content'];
64 die(xlt('Invalid File'));
67 if (($_POST['mode'] ??
null) === 'send_profiles') {
68 if (!empty($_POST['checked'])) {
69 $profiles = json_decode($_POST['checked']) ?
: [];
70 $last_id = $templateService->setProfileActiveStatus($profiles);
72 echo xlt('Profile Templates Successfully set to Active in portal.');
74 echo xlt('Error. Problem setting one or more profiles.');
78 die(xlt('Invalid Request'));
81 if (($_POST['mode'] ??
null) === 'send') {
82 if (!empty($_POST['docid'])) {
83 $pids_array = json_decode($_POST['docid']) ?
: ['0'];
84 // profiles are in an array with flag to indicate a group of template id's
85 $ids = json_decode($_POST['checked']) ?
: [];
87 foreach ($ids as $id) {
89 if ($id[1] !== true) {
93 // get all template ids for this profile
94 $rtn_ids = sqlStatement('SELECT `template_id` as id FROM `document_template_profiles` WHERE `profile` = ? AND `template_id` > "0"', array($profile));
95 while ($rtn_id = sqlFetchArray($rtn_ids)) {
96 $master_ids[$rtn_id['id']] = $profile;
100 $master_ids[$id] = '';
102 $last_id = $templateService->sendTemplate($pids_array, $master_ids, $_POST['category']);
104 echo xlt('Templates Successfully sent to Locations.');
106 echo xlt('Error. Problem sending one or more templates.');
110 die(xlt('Invalid Request'));
113 if (($_POST['mode'] ??
null) === 'save') {
114 if (!CsrfUtils
::verifyCsrfToken($_POST["csrf_token_form"], 'import-template-save')) {
115 CsrfUtils
::csrfNotVerified();
117 if (!$authUploadTemplates) {
118 die(xlt('Not authorized to edit template'));
120 if ($_POST['docid']) {
121 if (stripos($_POST['content'], "<?php") === false) {
122 $template = $templateService->updateTemplateContent($_POST['docid'], $_POST['content']);
123 if ($_POST['service'] === 'window') {
124 echo "<script>if (typeof parent.dlgopen === 'undefined') window.close(); else parent.dlgclose();</script>";
127 die(xlt('Invalid Content'));
130 die(xlt('Invalid File'));
132 } elseif (($_POST['mode'] ??
null) === 'delete') {
133 if (!CsrfUtils
::verifyCsrfToken($_POST["csrf_token_form"], 'import-template-delete')) {
134 CsrfUtils
::csrfNotVerified();
136 if (!$authUploadTemplates) {
137 die(xlt('Not authorized to delete template'));
139 if ($_POST['docid']) {
140 $template = $templateService->deleteTemplate($_POST['docid'], ($_POST['template'] ??
null));
143 die(xlt('Invalid File'));
144 } elseif (($_POST['mode'] ??
null) === 'update_category') {
145 if ($_POST['docid']) {
146 $template = $templateService->updateTemplateCategory($_POST['docid'], $_POST['category']);
147 echo xlt('Template Category successfully changed to new Category') . ' ' . text($_POST['category']);
150 die(xlt('Invalid Request Parameters'));
151 } elseif (!empty($_FILES["template_files"])) {
152 if (!CsrfUtils
::verifyCsrfToken($_POST["csrf_token_form"], 'import-template-upload')) {
153 CsrfUtils
::csrfNotVerified();
155 if (!$authUploadTemplates) {
156 xlt("Not Authorized to Upload Templates");
159 // so it is a template file import. create record(s).
160 $import_files = $_FILES["template_files"];
161 $total = count($_FILES['template_files']['name']);
162 for ($i = 0; $i < $total; $i++
) {
163 if ($_FILES['template_files']['error'][$i] !== UPLOAD_ERR_OK
) {
164 header('refresh:3;url= import_template_ui.php');
165 echo '<title>' . xlt('Error') . " ...</title><h4 style='color:red;'>" .
166 xlt('An error occurred: Missing file to upload. Returning to form.') . '</h4>';
169 // parse out what we need
170 $name = preg_replace("/[^A-Z0-9.]/i", " ", $_FILES['template_files']['name'][$i]);
171 if (preg_match("/(.*)\.(php|php7|php8|doc|docx)$/i", $name) !== 0) {
172 die(xlt('Invalid file type.'));
174 $parts = pathinfo($name);
175 $name = ucwords(strtolower($parts["filename"]));
176 if (empty($patient)) {
179 // get em and dispose
180 $success = $templateService->uploadTemplate($name, $_POST['template_category'], $_FILES['template_files']['tmp_name'][$i], $patient, isset($_POST['upload_submit_questionnaire']));
182 echo "<p>" . xlt("Unable to save files. Use back button!") . "</p>";
186 header("location: " . $_SERVER['HTTP_REFERER']);
190 if ($_REQUEST['mode'] === 'editor_render_html') {
191 if ($_REQUEST['docid']) {
192 $content = $templateService->fetchTemplate($_REQUEST['docid']);
193 $template_content = $content['template_content'];
194 if ($content['mime'] === 'application/pdf') {
195 $content = "<iframe width='100%' height='100%' src='data:application/pdf;base64, " .
196 attr(base64_encode($template_content)) . "'></iframe>";
200 renderEditorHtml($_REQUEST['docid'], $template_content);
202 die(xlt('Invalid File'));
204 } elseif (!empty($_GET['templateHtml'] ??
null)) {
205 renderEditorHtml($_REQUEST['docid'], $_GET['templateHtml']);
209 * @param $template_id
212 function renderEditorHtml($template_id, $content)
214 global $authUploadTemplates;
217 '{ParseAsHTML}', '{SignaturesRequired}', '{TextInput}', '{sizedTextInput:120px}', '{smTextInput}', '{TextBox:03x080}', '{CheckMark}', '{ynRadioGroup}', '{TrueFalseRadioGroup}', '{DatePicker}', '{DateTimePicker}', '{StandardDatePicker}', '{CurrentDate:"global"}', '{CurrentTime}', '{DOS}', '{ReferringDOC}', '{PatientID}', '{PatientName}', '{PatientSex}', '{PatientDOB}', '{PatientPhone}', '{Address}', '{City}', '{State}', '{Zip}', '{PatientSignature}', '{AdminSignature}', '{WitnessSignature}', '{AcknowledgePdf:pdf name or id:title}', '{EncounterForm:LBF}', '{Questionnaire:name or id}', '{QuestionnaireURLLoinc|name|https://clinicaltables.nlm.nih.gov/loinc_form_definitions|LOINC code}', '{Medications}', '{ProblemList}', '{Allergies}', '{ChiefComplaint}', '{DEM: }', '{HIS: }', '{LBF: }', '{GRP}{/GRP}'
223 <?php Header
::setupHeader(['ckeditor']); ?
>
228 outline
: 0 !important
;
229 -webkit
-appearance
: none
;
230 box
-shadow
: none
!important
;
238 height
: 78vh
!important
;
242 <div
class="container-fluid">
244 <div
class="col-10 px-1 sticky-top">
245 <form
class="sticky-top" action
='./import_template.php' method
='post'>
246 <input type
="hidden" name
="csrf_token_form" id
="csrf_token_form" value
="<?php echo attr(CsrfUtils::collectCsrfToken('import-template-save')); ?>" />
247 <input type
="hidden" name
="docid" value
="<?php echo attr($template_id) ?>">
248 <input type
='hidden' name
='mode' value
="save">
249 <input type
='hidden' name
='service' value
='window'>
250 <textarea cols
='80' rows
='10' id
='templateContent' name
='content'><?php
echo text($content) ?
></textarea
>
251 <div
class="row btn-group mt-1 float-right">
252 <div
class='col btn-group mt-1 float-right'>
253 <?php
if ($authUploadTemplates) { ?
>
254 <button type
="submit" class="btn btn-sm btn-primary"><?php
echo xlt("Save"); ?
></button
>
256 <button disabled title
="<?php echo xla("Not Authorized to Edit Templates
") ?>" type
="submit" class="btn btn-sm btn-primary"><?php
echo xlt("Save"); ?
></button
>
258 <button type
='button' class='btn btn-sm btn-secondary' onclick
='parent.window.close() || parent.dlgclose()'><?php
echo xlt('Cancel'); ?
></button
>
263 <div
class="col-sm-2 px-0">
264 <div
class='h4'><?php
echo xlt("Directives") ?
></div
>
265 <ul
class='list-group list-group-flush pl-1 mb-5'>
267 foreach ($lists as $list) {
268 echo '<input class="list-group-item p-1" value="' . attr($list) . '">';
277 let isDialog
= false;
280 <?php
if (!empty($_REQUEST['dialog'] ??
'')) { ?
>
286 document
.addEventListener('DOMContentLoaded', function () {
287 document
.querySelectorAll('.list-group-item').forEach(item
=> {
288 item
.addEventListener('mouseup', event
=> {
289 let input
= event
.currentTarget
;
294 editor
= CKEDITOR
.instances
['templateContent'];
296 editor
.destroy(true);
298 CKEDITOR
.disableAutoInline
= true;
299 CKEDITOR
.config
.extraPlugins
= "preview,save,docprops,justify";
300 CKEDITOR
.config
.allowedContent
= true;
301 //CKEDITOR.config.fullPage = true;
302 CKEDITOR
.config
.height
= height
;
303 CKEDITOR
.config
.width
= '100%';
304 CKEDITOR
.config
.resize_dir
= 'both';
305 CKEDITOR
.config
.resize_minHeight
= max
/ 2;
306 CKEDITOR
.config
.resize_maxHeight
= max
;
307 CKEDITOR
.config
.resize_minWidth
= '50%';
308 CKEDITOR
.config
.resize_maxWidth
= '100%';
310 editor
= CKEDITOR
.replace('templateContent', {
311 removeButtons
: 'PasteFromWord'
321 function renderProfileHtml()
323 global $templateService;
325 $category_list = $templateService->fetchDefaultCategories();
326 $profile_list = $templateService->fetchDefaultProfiles();
332 if (empty($GLOBALS['openemr_version'] ??
null)) {
333 Header
::setupHeader(['opener', 'sortablejs']);
335 Header
::setupHeader(['opener']); ?
>
336 <script src
="<?php echo $GLOBALS['web_root']; ?>/portal/public/assets/sortablejs/Sortable.min.js?v=<?php echo $GLOBALS['v_js_includes']; ?>"></script
>
359 const profiles
= <?php
echo js_escape($profile_list); ?
>;
360 document
.addEventListener('DOMContentLoaded', function () {
361 // init drag and drop
362 let repository
= document
.getElementById('drag_repository');
363 Sortable
.create(repository
, {
366 handle
: '.move-handle',
371 onAdd
: function (evt
) {
373 el
.parentNode
.removeChild(el
);
377 Object.keys(profiles
).forEach(key
=> {
378 let profileEl
= profiles
[key
]['option_id']
379 let id
= document
.getElementById(profileEl
);
380 Sortable
.create(id
, {
384 handle
: '.move-handle',
385 put
: (to
, from
, dragEl
, event
) => {
386 for (let i
= 0; i
< to
.el
.children
.length
; i++
) {
387 if (to
.el
.children
[i
].getAttribute('data-id') === dragEl
.getAttribute('data-id')) {
399 function submitProfiles() {
400 top
.restoreSession();
401 let target
= document
.getElementById('edit-profiles');
402 let profileTarget
= target
.querySelectorAll('ul');
403 let formTarget
= target
.querySelectorAll('form');
404 let profileArray
= [];
406 profileTarget
.forEach((ulItem
, index
) => {
407 let lists
= ulItem
.querySelectorAll('li');
408 lists
.forEach((item
, index
) => {
409 //console.log({index, item})
410 let pform
= document
.getElementById(ulItem
.dataset
.profile +
'-form');
411 let formData
= $
(pform
).serializeArray();
414 'profile': ulItem
.dataset
.profile
,
415 'id': item
.dataset
.id
,
416 'category': item
.dataset
.category
,
417 'name': item
.dataset
.name
419 profileArray
.push(listData
);
422 const data
= new FormData();
423 data
.append('profiles', JSON
.stringify(profileArray
));
424 data
.append('mode', 'save_profiles');
425 fetch('./import_template.php', {
428 }).then(rtn
=> rtn
.text()).then((rtn
) => {
430 await
asyncAlertMsg(rtn
, time
, 'success', 'lg');
431 })(1000).then(rtn
=> {
432 opener
.document
.edit_form
.submit();
435 }).catch((error
) => {
436 console
.error('Error:', error
);
441 <div
class='container-fluid'>
443 // exclude templates sent to all patients(defaults)
444 $templates = $templateService->getTemplateListAllCategories(-1, true);
445 //$templates = $templateService->getTemplateListUnique(); // Reserved TBD future use
448 <div
class='col-6 col-height'>
449 <nav id
='disposeProfile' class='navbar navbar-light bg-light sticky-top'>
450 <div
class='btn-group'>
451 <button
class='btn btn-primary btn-save btn-sm' onclick
='return submitProfiles();'><?php
echo xlt('Save Profiles'); ?
></button
>
452 <button
class='btn btn-secondary btn-cancel btn-sm' onclick
='dlgclose();'><?php
echo xlt('Quit'); ?
></button
>
455 <div
class="border-left border-right">
456 <div
class='bg-dark text-light py-1 text-center'><?php
echo xlt('Available Templates'); ?
></div
>
457 <ul id
='drag_repository' class='list-group mx-2 mb-2'>
459 foreach ($templates as $cat => $files) {
461 $cat = xlt('General');
463 foreach ($files as $file) {
464 $template_id = attr($file['id']);
465 $title = $category_list[$cat]['title'] ?
: $cat;
466 $title_esc = attr($title);
467 $this_name = attr($file['template_name']);
468 if ($file['mime'] === 'application/pdf') {
471 echo "<li class='list-group-item px-1 py-1 mb-2' data-id='$template_id' data-name='$this_name' data-category='$title_esc'>" .
472 "<strong>" . text($file['template_name']) .
473 '</strong>' . ' ' . xlt('in category') . ' ' .
474 '<strong>' . text($title) . '</strong>' . '</li>' . "\n";
481 <div
class='col-6 col-height'>
482 <div id
="edit-profiles" class='control-group mx-1 border-left border-right'>
484 foreach ($profile_list as $profile => $profiles) {
485 $profile_items_list = $templateService->getTemplateListByProfile($profile);
486 $profile_esc = attr($profile);
487 $events = $templateService->fetchAllProfileEvents();
488 $recurring = attr($events[$profile]['recurring'] ??
'');
489 $trigger = attr($events[$profile]['event_trigger'] ??
'');
490 $days = attr($events[$profile]['period'] ??
'');
492 <form id
="<?php echo $profile_esc ?>-form" name
="<?php echo $profile_esc; ?>" class='form form-inline bg-dark text-light py-1 pl-1'>
493 <label
class='mr-1'><?php
echo xlt($profiles['title']) ?
></label
>
494 <div
class='input-group-prepend ml-auto'>
495 <label
for="<?php echo $profile_esc ?>-recurring" class="form-check-inline"><?php
echo xlt('Recurring') ?
>
496 <input
<?php
echo $recurring ?
'checked' : '' ?
> name
="recurring" type
='checkbox' class="input-control ml-1 mt-1" id
="<?php echo $profile_esc ?>-recurring" />
499 <!-- @TODO Hide
for now until sensible events can be determined
. -->
500 <div
class='input-group-prepend d-none'>
501 <label
for="<?php echo $profile_esc ?>-when"><?php
echo xlt('On') ?
></label
>
502 <select name
="when" class='input-control-sm mx-1' id
="<?php echo $profile_esc ?>-when">
503 <!--<option value
=""><?php
/*echo xlt('Unassigned') */?
></option
>-->
504 <option
<?php
echo $trigger === 'completed' ?
'selected' : ''; ?
> value
="completed"><?php
echo xlt('Completed') ?
></option
>
505 <option
<?php
echo $trigger === 'always' ?
'selected' : ''; ?
> value
='always'><?php
echo xlt('Always') ?
></option
>
506 <option
<?php
echo $trigger === 'once' ?
'selected' : ''; ?
> value
='once'><?php
echo xlt('One time') ?
></option
>
509 <div
class='input-group-prepend'>
510 <label
for="<?php echo $profile_esc ?>-days"><?php
echo xlt('Every') ?
></label
>
511 <input name
="days" type
="text" style
="width: 50px" class='input-control-sm ml-1' id
="<?php echo $profile_esc ?>-days" placeholder
="<?php echo xla('days') ?>" value
="<?php echo $days ?>" />
512 <label
class="mx-1" for="<?php echo $profile_esc ?>-days"><?php
echo xlt('Days') ?
></label
>
516 echo "<ul id='$profile_esc' class='list-group mx-2 mb-2' data-profile='$profile_esc'>\n";
517 foreach ($profile_items_list as $cat => $files) {
519 $cat = xlt('General');
521 foreach ($files as $file) {
522 $template_id = attr($file['id']);
523 $this_cat = attr($file['category']);
524 $title = $category_list[$file['category']]['title'] ?
: $cat;
525 $this_name = attr($file['template_name']);
526 if ($file['mime'] === 'application/pdf') {
529 echo "<li class='list-group-item px-1 py-1 mb-2' data-id='$template_id' data-name='$this_name' data-category='$this_cat'>" .
530 text($file['template_name']) . ' ' . xlt('in category') . ' ' . text($title) . "</li>\n";