3 * Document Template Download Module.
5 * Copyright (C) 2013-2014 Rod Roark <rod@sunsetsystems.com>
6 * Copyright (C) 2016-2019 Jerry Padgett <sjpadgett@gmail.com>
8 * LICENSE: This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>.
20 * @author Rod Roark <rod@sunsetsystems.com>
21 * @author Jerry Padgett <sjpadgett@gmail.com>
22 * @link http://www.open-emr.org
25 // This module downloads a specified document template to the browser after
26 // substituting relevant patient data into its variables.
27 $is_module = isset($_POST['isModule']) ?
$_POST['isModule'] : 0;
29 require_once(dirname(__file__
) . '/../../interface/globals.php');
31 require_once(dirname(__file__
) . "/../verify_session.php");
34 require_once($GLOBALS['srcdir'] . '/acl.inc');
35 require_once($GLOBALS['srcdir'] . '/appointments.inc.php');
36 require_once($GLOBALS['srcdir'] . '/options.inc.php');
38 $form_filename = $_POST['docid'];
40 $user = $_SESSION['authUserID'];
42 $nextLocation = 0; // offset to resume scanning
43 $keyLocation = false; // offset of a potential {string} to replace
44 $keyLength = 0; // length of {string} to replace
45 $groupLevel = 0; // 0 if not in a {GRP} section
46 $groupCount = 0; // 0 if no items in the group yet
47 $itemSeparator = '; '; // separator between group items
48 $tcnt = $grcnt = $ckcnt = 0;
51 // Flags to ignore new lines
53 // Check if the current location has the specified {string}.
54 function keySearch(&$s, $key)
56 global $keyLocation, $keyLength;
57 $keyLength = strlen($key);
58 if ($keyLength == 0) {
62 return $key == substr($s, $keyLocation, $keyLength);
65 // Replace the {string} at the current location with the specified data.
66 // Also update the location to resume scanning accordingly.
67 function keyReplace(&$s, $data)
69 global $keyLocation, $keyLength, $nextLocation;
70 $nextLocation = $keyLocation +
strlen($data);
71 return substr($s, 0, $keyLocation) . $data . substr($s, $keyLocation +
$keyLength);
74 // Do some final processing of field data before it's put into the document.
75 function dataFixup($data, $title = '')
77 global $groupLevel, $groupCount, $itemSeparator;
79 // Replace some characters that can mess up XML without assuming XML content type.
80 $data = str_replace('&', '[and]', $data);
81 $data = str_replace('<', '[less]', $data);
82 $data = str_replace('>', '[greater]', $data);
83 // If in a group, include labels and separators.
86 $data = $title . ': ' . $data;
90 $data = $itemSeparator . $data;
100 // Return a string naming all issues for the specified patient and issue type.
101 function getIssues($type)
103 // global $itemSeparator;
105 $lres = sqlStatement("SELECT title, comments FROM lists WHERE " . "pid = ? AND type = ? AND enddate IS NULL " . "ORDER BY begdate", array(
109 while ($lrow = sqlFetchArray($lres)) {
114 $tmp .= $lrow['title'];
115 if ($lrow['comments']) {
116 $tmp .= ' (' . $lrow['comments'] . ')';
123 // Top level function for scanning and replacement of a file's contents.
126 global $ptrow, $hisrow, $enrow, $nextLocation, $keyLocation, $keyLength;
127 global $groupLevel, $groupCount, $itemSeparator, $pid, $user, $encounter;
128 global $tcnt, $grcnt, $ckcnt, $is_module;
134 while (($keyLocation = strpos($s, '{', $nextLocation)) !== false) {
135 $nextLocation = $keyLocation +
1;
137 if (keySearch($s, '{PatientSignature}')) {
139 $sigfld .= '<img class="signature" id="patientSignature" style="cursor:pointer;color:red;height:70px;width:auto;" data-type="patient-signature" data-action="fetch_signature" alt="' . xla("Click in signature") . '" data-pid="' . attr((int)$pid) . '" data-user="' . attr((int)$user) . '" src="">';
140 $sigfld .= '</span>';
141 $s = keyReplace($s, $sigfld);
142 } else if (keySearch($s, '{AdminSignature}')) {
144 $sigfld .= '<img class="signature" id="adminSignature" style="cursor:pointer;color:red;height:70px;width:auto;" data-type="admin-signature" data-action="fetch_signature" alt="' . xla("Click in signature") . '" data-pid="' . attr((int)$pid) . '" data-user="' . attr((int)$user) . '" src="">';
145 $sigfld .= '</span>';
146 $s = keyReplace($s, $sigfld);
147 } else if (keySearch($s, '{ParseAsHTML}')) {
149 $s = keyReplace($s, "");
150 } else if (keySearch($s, '{TextBox}')) {
152 $sigfld .= '<textarea class="templateInput" rows="3" cols="40" style="margin:2px auto;" data-textvalue="" onblur="templateText(this);"></textarea>';
153 $sigfld .= '</span>';
154 $s = keyReplace($s, $sigfld);
155 } else if (keySearch($s, '{TextInput}')) {
157 $sigfld .= '<input class="templateInput" type="text" style="margin:2px auto;" data-textvalue="" onblur="templateText(this);">';
158 $sigfld .= '</span>';
159 $s = keyReplace($s, $sigfld);
160 } else if (keySearch($s, '{smTextInput}')) {
162 $sigfld .= '<input class="templateInput" type="text" style="margin:2px auto;max-width:50px;" data-textvalue="" onblur="templateText(this);">';
163 $sigfld .= '</span>';
164 $s = keyReplace($s, $sigfld);
165 } else if (keySearch($s, '{CheckMark}')) {
167 $sigfld = '<span class="checkMark" data-id="check' . $ckcnt . '">';
168 $sigfld .= '<input type="checkbox" id="check' . $ckcnt . '" data-value="" onclick="templateCheckMark(this);">';
169 $sigfld .= '</span>';
170 $s = keyReplace($s, $sigfld);
171 } else if (keySearch($s, '{ynRadioGroup}')) {
173 $sigfld = '<span class="ynuGroup" data-value="N/A" data-id="' . $grcnt . '" id="rgrp' . $grcnt . '">';
174 $sigfld .= '<label><input onclick="templateRadio(this)" type="radio" name="ynradio' . $grcnt . '" data-id="' . $grcnt . '" value="Yes">' . xlt("Yes") . '</label>';
175 $sigfld .= '<label><input onclick="templateRadio(this)" type="radio" name="ynradio' . $grcnt . '" data-id="' . $grcnt . '" value="No">' . xlt("No") . '</label>';
176 $sigfld .= '<label><input onclick="templateRadio(this)" type="radio" name="ynradio' . $grcnt . '" checked="checked" data-id="' . $grcnt . '" value="Unk">Unk</label>';
177 $sigfld .= '</span>';
178 $s = keyReplace($s, $sigfld);
179 } else if (keySearch($s, '{PatientName}')) {
180 $tmp = $ptrow['fname'];
181 if ($ptrow['mname']) {
186 $tmp .= $ptrow['mname'];
189 if ($ptrow['lname']) {
194 $tmp .= $ptrow['lname'];
196 $s = keyReplace($s, dataFixup($tmp, xl('Name')));
197 } else if (keySearch($s, '{PatientID}')) {
198 $s = keyReplace($s, dataFixup($ptrow['pubpid'], xl('Chart ID')));
199 } else if (keySearch($s, '{Address}')) {
200 $s = keyReplace($s, dataFixup($ptrow['street'], xl('Street')));
201 } else if (keySearch($s, '{City}')) {
202 $s = keyReplace($s, dataFixup($ptrow['city'], xl('City')));
203 } else if (keySearch($s, '{State}')) {
204 $s = keyReplace($s, dataFixup(getListItemTitle('state', $ptrow['state']), xl('State')));
205 } else if (keySearch($s, '{Zip}')) {
206 $s = keyReplace($s, dataFixup($ptrow['postal_code'], xl('Postal Code')));
207 } else if (keySearch($s, '{PatientPhone}')) {
208 $ptphone = $ptrow['phone_contact'];
209 if (empty($ptphone)) {
210 $ptphone = $ptrow['phone_home'];
213 if (empty($ptphone)) {
214 $ptphone = $ptrow['phone_cell'];
217 if (empty($ptphone)) {
218 $ptphone = $ptrow['phone_biz'];
221 if (preg_match("/([2-9]\d\d)\D*(\d\d\d)\D*(\d\d\d\d)/", $ptphone, $tmp)) {
222 $ptphone = '(' . $tmp[1] . ')' . $tmp[2] . '-' . $tmp[3];
225 $s = keyReplace($s, dataFixup($ptphone, xl('Phone')));
226 } else if (keySearch($s, '{PatientDOB}')) {
227 $s = keyReplace($s, dataFixup(oeFormatShortDate($ptrow['DOB']), xl('Birth Date')));
228 } else if (keySearch($s, '{PatientSex}')) {
229 $s = keyReplace($s, dataFixup(getListItemTitle('sex', $ptrow['sex']), xl('Sex')));
230 } else if (keySearch($s, '{DOS}')) {
231 // $s = @keyReplace($s, dataFixup(oeFormatShortDate(substr($enrow['date'], 0, 10)), xl('Service Date'))); // changed DOS to todays date- add future enc DOS
232 $s = @keyReplace
($s, dataFixup(oeFormatShortDate(substr(date("Y-m-d"), 0, 10)), xl('Service Date')));
233 } else if (keySearch($s, '{ChiefComplaint}')) {
234 $cc = $enrow['reason'];
235 $patientid = $ptrow['pid'];
236 $DOS = substr($enrow['date'], 0, 10);
237 // Prefer appointment comment if one is present.
238 $evlist = fetchEvents($DOS, $DOS, " AND pc_pid = ? ", null, false, 0, array($patientid));
239 foreach ($evlist as $tmp) {
240 if ($tmp['pc_pid'] == $pid && ! empty($tmp['pc_hometext'])) {
241 $cc = $tmp['pc_hometext'];
245 $s = keyReplace($s, dataFixup($cc, xl('Chief Complaint')));
246 } else if (keySearch($s, '{ReferringDOC}')) {
247 $tmp = empty($ptrow['ur_fname']) ?
'' : $ptrow['ur_fname'];
248 if (! empty($ptrow['ur_mname'])) {
253 $tmp .= $ptrow['ur_mname'];
256 if (! empty($ptrow['ur_lname'])) {
261 $tmp .= $ptrow['ur_lname'];
264 $s = keyReplace($s, dataFixup($tmp, xl('Referer')));
265 } else if (keySearch($s, '{Allergies}')) {
266 $tmp = generate_plaintext_field(array(
270 $s = keyReplace($s, dataFixup($tmp, xl('Allergies')));
271 } else if (keySearch($s, '{Medications}')) {
272 $s = keyReplace($s, dataFixup(getIssues('medication'), xl('Medications')));
273 } else if (keySearch($s, '{ProblemList}')) {
274 $s = keyReplace($s, dataFixup(getIssues('medical_problem'), xl('Problem List')));
275 } else if (keySearch($s, '{GRP}')) { // This tag indicates the fields from here until {/GRP} are a group of fields
276 // separated by semicolons. Fields with no data are omitted, and fields with
277 // data are prepended with their field label from the form layout.
280 $s = keyReplace($s, '');
281 } else if (keySearch($s, '{/GRP}')) {
282 if ($groupLevel > 0) {
286 $s = keyReplace($s, '');
287 } else if (preg_match('/^\{ITEMSEP\}(.*?)\{\/ITEMSEP\}/', substr($s, $keyLocation), $matches)) {
288 // This is how we specify the separator between group items in a way that
289 // is independent of the document format. Whatever is between {ITEMSEP} and
290 // {/ITEMSEP} is the separator string. Default is "; ".
291 $itemSeparator = $matches[1];
292 $keyLength = strlen($matches[0]);
293 $s = keyReplace($s, '');
294 } else if (preg_match('/^\{(LBF\w+):(\w+)\}/', substr($s, $keyLocation), $matches)) {
295 // This handles keys like {LBFxxx:fieldid} for layout-based encounter forms.
296 $formname = $matches[1];
297 $fieldid = $matches[2];
298 $keyLength = 3 +
strlen($formname) +
strlen($fieldid);
302 $frow = sqlQuery("SELECT * FROM layout_options " . "WHERE form_id = ? AND field_id = ? LIMIT 1", array(
306 if (! empty($frow)) {
307 $ldrow = sqlQuery("SELECT ld.field_value " . "FROM lbf_data AS ld, forms AS f WHERE " . "f.pid = ? AND f.encounter = ? AND f.formdir = ? AND f.deleted = 0 AND " . "ld.form_id = f.form_id AND ld.field_id = ? " . "ORDER BY f.form_id DESC LIMIT 1", array(
313 if (! empty($ldrow)) {
314 $currvalue = $ldrow['field_value'];
315 $title = $frow['title'];
318 if ($currvalue !== '') {
319 $data = generate_plaintext_field($frow, $currvalue);
323 $s = keyReplace($s, dataFixup($data, $title));
324 } else if (preg_match('/^\{(DEM|HIS):(\w+)\}/', substr($s, $keyLocation), $matches)) {
325 // This handles keys like {DEM:fieldid} and {HIS:fieldid}.
326 $formname = $matches[1];
327 $fieldid = $matches[2];
328 $keyLength = 3 +
strlen($formname) +
strlen($fieldid);
332 $frow = sqlQuery("SELECT * FROM layout_options " . "WHERE form_id = ? AND field_id = ? LIMIT 1", array(
336 if (! empty($frow)) {
337 $tmprow = $formname == 'DEM' ?
$ptrow : $hisrow;
338 if (isset($tmprow[$fieldid])) {
339 $currvalue = $tmprow[$fieldid];
340 $title = $frow['title'];
343 if ($currvalue !== '') {
344 $data = generate_plaintext_field($frow, $currvalue);
348 $s = keyReplace($s, dataFixup($data, $title));
350 } // End if { character found.
354 // Get patient demographic info. pd.ref_providerID
355 $ptrow = sqlQuery("SELECT pd.*, " . "ur.fname AS ur_fname, ur.mname AS ur_mname, ur.lname AS ur_lname, ur.title AS ur_title, ur.specialty AS ur_specialty " . "FROM patient_data AS pd " . "LEFT JOIN users AS ur ON ur.id = ? " . "WHERE pd.pid = ?", array($user, $pid));
357 $hisrow = sqlQuery("SELECT * FROM history_data WHERE pid = ? " . "ORDER BY date DESC LIMIT 1", array(
363 // Get some info for the currently selected encounter.
365 $enrow = sqlQuery("SELECT * FROM form_encounter WHERE pid = ? AND " . "encounter = ?", array(
371 $templatedir = $GLOBALS['OE_SITE_DIR'] . '/documents/onsite_portal_documents/templates';
373 check_file_dir_name($form_filename);
374 $templatepath = "$templatedir/$form_filename";
375 // test if this is folder with template, if not, must be for a specific patient
376 if (! file_exists($templatepath)) {
377 check_file_dir_name($pid);
378 $templatepath = "$templatedir/" . $pid . "/$form_filename";
381 $edata = file_get_contents($templatepath);
382 $edata = doSubs($edata);
384 if ($html_flag) { // return raw minified html template
385 $html = trim(str_replace(["\r\n", "\r", "\n"], '', $edata));
386 } else { // add br for lf in text template
387 $html = trim(str_replace(["\r\n", "\r", "\n"], '<br/>', $edata));