Questionnaire encounter report (#5757)
[openemr.git] / portal / import_template.php
blob2aac1899040741bbf67f037369a1c44eeae4b38c
1 <?php
3 /**
4 * import_template.php
6 * @package OpenEMR
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');
22 exit;
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);
36 if ($rtn) {
37 echo xlt("Profiles successfully saved.");
38 } else {
39 echo xlt('Error! Profiles save failed. Check your Profile lists.');
41 exit;
44 if (($_REQUEST['mode'] ?? null) === 'render_profile') {
45 echo renderProfileHtml();
46 exit;
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']);
53 exit();
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'];
62 exit();
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);
71 if ($last_id) {
72 echo xlt('Profile Templates Successfully set to Active in portal.');
73 } else {
74 echo xlt('Error. Problem setting one or more profiles.');
76 exit;
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']) ?: [];
86 $master_ids = [];
87 foreach ($ids as $id) {
88 if (is_array($id)) {
89 if ($id[1] !== true) {
90 continue;
92 $profile = $id[0];
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;
98 continue;
100 $master_ids[$id] = '';
102 $last_id = $templateService->sendTemplate($pids_array, $master_ids, $_POST['category']);
103 if ($last_id) {
104 echo xlt('Templates Successfully sent to Locations.');
105 } else {
106 echo xlt('Error. Problem sending one or more templates.');
108 exit;
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>";
126 } else {
127 die(xlt('Invalid Content'));
129 } else {
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));
141 exit($template);
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']);
148 exit;
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");
157 exit;
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>';
167 exit;
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)) {
177 $patient = ['-1'];
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']));
181 if (!$success) {
182 echo "<p>" . xlt("Unable to save files. Use back button!") . "</p>";
183 exit;
186 header("location: " . $_SERVER['HTTP_REFERER']);
187 die();
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>";
197 echo $content;
198 exit;
200 renderEditorHtml($_REQUEST['docid'], $template_content);
201 } else {
202 die(xlt('Invalid File'));
204 } elseif (!empty($_GET['templateHtml'] ?? null)) {
205 renderEditorHtml($_REQUEST['docid'], $_GET['templateHtml']);
209 * @param $template_id
210 * @param $content
212 function renderEditorHtml($template_id, $content)
214 global $authUploadTemplates;
216 $lists = [
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}'
220 <!DOCTYPE html>
221 <html>
222 <head>
223 <?php Header::setupHeader(['ckeditor']); ?>
224 </head>
225 <style>
226 input:focus,
227 input:active {
228 outline: 0 !important;
229 -webkit-appearance: none;
230 box-shadow: none !important;
233 .list-group-item {
234 font-size: .9rem;
237 .cke_contents {
238 height: 78vh !important;
240 </style>
241 <body>
242 <div class="container-fluid">
243 <div class="row">
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>
255 <?php } else { ?>
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>
257 <?php } ?>
258 <button type='button' class='btn btn-sm btn-secondary' onclick='parent.window.close() || parent.dlgclose()'><?php echo xlt('Cancel'); ?></button>
259 </div>
260 </div>
261 </form>
262 </div>
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'>
266 <?php
267 foreach ($lists as $list) {
268 echo '<input class="list-group-item p-1" value="' . attr($list) . '">';
271 </ul>
272 </div>
273 </div>
274 </div>
275 </body>
276 <script>
277 let isDialog = false;
278 let height = 550;
279 let max = 680;
280 <?php if (!empty($_REQUEST['dialog'] ?? '')) { ?>
281 isDialog = true;
282 height = 425;
283 max = 600;
284 <?php } ?>
285 let editor = '';
286 document.addEventListener('DOMContentLoaded', function () {
287 document.querySelectorAll('.list-group-item').forEach(item => {
288 item.addEventListener('mouseup', event => {
289 let input = event.currentTarget;
290 input.focus();
291 input.select();
294 editor = CKEDITOR.instances['templateContent'];
295 if (editor) {
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'
314 </script>
315 </html>
316 <?php }
321 function renderProfileHtml()
323 global $templateService;
325 $category_list = $templateService->fetchDefaultCategories();
326 $profile_list = $templateService->fetchDefaultProfiles();
328 <!DOCTYPE html>
329 <html>
330 <head>
331 <?php
332 if (empty($GLOBALS['openemr_version'] ?? null)) {
333 Header::setupHeader(['opener', 'sortablejs']);
334 } else {
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>
337 <?php } ?>
338 </head>
339 <style>
340 body {
341 overflow: hidden;
344 .list-group-item {
345 cursor: move;
348 strong {
349 font-weight: 600;
352 .col-height {
353 max-height: 95vh;
354 overflow-y: auto;
355 overflow-x: hidden;
357 </style>
358 <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, {
364 group: {
365 name: 'repo',
366 handle: '.move-handle',
367 pull: 'clone'
369 sort: true,
370 animation: 150,
371 onAdd: function (evt) {
372 let el = evt.item;
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, {
381 group: {
382 name: 'repo',
383 delay: 1000,
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')) {
388 return false
391 return true
394 animation: 150
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 = [];
405 let listData = {};
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();
412 listData = {
413 'form': formData,
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', {
426 method: 'POST',
427 body: data,
428 }).then(rtn => rtn.text()).then((rtn) => {
429 (async (time) => {
430 await asyncAlertMsg(rtn, time, 'success', 'lg');
431 })(1000).then(rtn => {
432 opener.document.edit_form.submit();
433 dlgclose();
435 }).catch((error) => {
436 console.error('Error:', error);
439 </script>
440 <body>
441 <div class='container-fluid'>
442 <?php
443 // exclude templates sent to all patients(defaults)
444 $templates = $templateService->getTemplateListAllCategories(-1, true);
445 //$templates = $templateService->getTemplateListUnique(); // Reserved TBD future use
447 <div class='row'>
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>
453 </div>
454 </nav>
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'>
458 <?php
459 foreach ($templates as $cat => $files) {
460 if (empty($cat)) {
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') {
469 continue;
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";
478 </ul>
479 </div>
480 </div>
481 <div class='col-6 col-height'>
482 <div id="edit-profiles" class='control-group mx-1 border-left border-right'>
483 <?php
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" />
497 </label>
498 </div>
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>
507 </select>
508 </div>
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>
513 </div>
514 </form>
515 <?php
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) {
518 if (empty($cat)) {
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') {
527 continue;
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";
533 echo "</ul>\n";
536 </div>
537 </div>
538 </div>
539 </div>
540 <hr />
541 </body>
542 </html>
543 <?php }