Highway to PSR2
[openemr.git] / interface / patient_file / download_template.php
blob6fb7fa266871f537a0b75d8173773e148ee1163b
1 <?php
2 /**
3 * Document Template Download Module.
5 * Copyright (C) 2013-2014 Rod Roark <rod@sunsetsystems.com>
7 * LICENSE: This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>.
18 * @package OpenEMR
19 * @author Rod Roark <rod@sunsetsystems.com>
20 * @link http://www.open-emr.org
23 // This module downloads a specified document template to the browser after
24 // substituting relevant patient data into its variables.
26 require_once('../globals.php');
27 require_once($GLOBALS['srcdir'] . '/acl.inc');
28 require_once($GLOBALS['srcdir'] . '/appointments.inc.php');
29 require_once($GLOBALS['srcdir'] . '/options.inc.php');
31 $nextLocation = 0; // offset to resume scanning
32 $keyLocation = false; // offset of a potential {string} to replace
33 $keyLength = 0; // length of {string} to replace
34 $groupLevel = 0; // 0 if not in a {GRP} section
35 $groupCount = 0; // 0 if no items in the group yet
36 $itemSeparator = '; '; // separator between group items
38 // Check if the current location has the specified {string}.
39 function keySearch(&$s, $key)
41 global $keyLocation, $keyLength;
42 $keyLength = strlen($key);
43 if ($keyLength == 0) {
44 return false;
47 return $key == substr($s, $keyLocation, $keyLength);
50 // Replace the {string} at the current location with the specified data.
51 // Also update the location to resume scanning accordingly.
52 function keyReplace(&$s, $data)
54 global $keyLocation, $keyLength, $nextLocation;
55 $nextLocation = $keyLocation + strlen($data);
56 return substr($s, 0, $keyLocation) . $data . substr($s, $keyLocation + $keyLength);
59 // Do some final processing of field data before it's put into the document.
60 function dataFixup($data, $title = '')
62 global $groupLevel, $groupCount, $itemSeparator;
63 if ($data !== '') {
64 // Replace some characters that can mess up XML without assuming XML content type.
65 $data = str_replace('&', '[and]', $data);
66 $data = str_replace('<', '[less]', $data);
67 $data = str_replace('>', '[greater]', $data);
68 // If in a group, include labels and separators.
69 if ($groupLevel) {
70 if ($title !== '') {
71 $data = $title . ': ' . $data;
74 if ($groupCount) {
75 $data = $itemSeparator . $data;
78 ++$groupCount;
82 return $data;
85 // Return a string naming all issues for the specified patient and issue type.
86 function getIssues($type)
88 // global $itemSeparator;
89 $tmp = '';
90 $lres = sqlStatement("SELECT title, comments FROM lists WHERE " .
91 "pid = ? AND type = ? AND enddate IS NULL " .
92 "ORDER BY begdate", array($GLOBALS['pid'], $type));
93 while ($lrow = sqlFetchArray($lres)) {
94 if ($tmp) {
95 $tmp .= '; ';
98 $tmp .= $lrow['title'];
99 if ($lrow['comments']) {
100 $tmp .= ' (' . $lrow['comments'] . ')';
104 return $tmp;
107 // Top level function for scanning and replacement of a file's contents.
108 function doSubs($s)
110 global $ptrow, $hisrow, $enrow, $nextLocation, $keyLocation, $keyLength;
111 global $groupLevel, $groupCount, $itemSeparator, $pid, $encounter;
113 $nextLocation = 0;
114 $groupLevel = 0;
115 $groupCount = 0;
117 while (($keyLocation = strpos($s, '{', $nextLocation)) !== false) {
118 $nextLocation = $keyLocation + 1;
120 if (keySearch($s, '{PatientName}')) {
121 $tmp = $ptrow['fname'];
122 if ($ptrow['mname']) {
123 if ($tmp) {
124 $tmp .= ' ';
127 $tmp .= $ptrow['mname'];
130 if ($ptrow['lname']) {
131 if ($tmp) {
132 $tmp .= ' ';
135 $tmp .= $ptrow['lname'];
138 $s = keyReplace($s, dataFixup($tmp, xl('Name')));
139 } else if (keySearch($s, '{PatientID}')) {
140 $s = keyReplace($s, dataFixup($ptrow['pubpid'], xl('Chart ID')));
141 } else if (keySearch($s, '{Address}')) {
142 $s = keyReplace($s, dataFixup($ptrow['street'], xl('Street')));
143 } else if (keySearch($s, '{City}')) {
144 $s = keyReplace($s, dataFixup($ptrow['city'], xl('City')));
145 } else if (keySearch($s, '{State}')) {
146 $s = keyReplace($s, dataFixup(getListItemTitle('state', $ptrow['state']), xl('State')));
147 } else if (keySearch($s, '{Zip}')) {
148 $s = keyReplace($s, dataFixup($ptrow['postal_code'], xl('Postal Code')));
149 } else if (keySearch($s, '{PatientPhone}')) {
150 $ptphone = $ptrow['phone_contact'];
151 if (empty($ptphone)) {
152 $ptphone = $ptrow['phone_home'];
155 if (empty($ptphone)) {
156 $ptphone = $ptrow['phone_cell'];
159 if (empty($ptphone)) {
160 $ptphone = $ptrow['phone_biz'];
163 if (preg_match("/([2-9]\d\d)\D*(\d\d\d)\D*(\d\d\d\d)/", $ptphone, $tmp)) {
164 $ptphone = '(' . $tmp[1] . ')' . $tmp[2] . '-' . $tmp[3];
167 $s = keyReplace($s, dataFixup($ptphone, xl('Phone')));
168 } else if (keySearch($s, '{PatientDOB}')) {
169 $s = keyReplace($s, dataFixup(oeFormatShortDate($ptrow['DOB']), xl('Birth Date')));
170 } else if (keySearch($s, '{PatientSex}')) {
171 $s = keyReplace($s, dataFixup(getListItemTitle('sex', $ptrow['sex']), xl('Sex')));
172 } else if (keySearch($s, '{DOS}')) {
173 $s = keyReplace($s, dataFixup(oeFormatShortDate(substr($enrow['date'], 0, 10)), xl('Service Date')));
174 } else if (keySearch($s, '{ChiefComplaint}')) {
175 $cc = $enrow['reason'];
176 $patientid = $ptrow['pid'];
177 $DOS = substr($enrow['date'], 0, 10);
178 // Prefer appointment comment if one is present.
179 $evlist = fetchEvents($DOS, $DOS, " AND pc_pid = ? ", null, false, 0, array($patientid));
180 foreach ($evlist as $tmp) {
181 if ($tmp['pc_pid'] == $pid && !empty($tmp['pc_hometext'])) {
182 $cc = $tmp['pc_hometext'];
186 $s = keyReplace($s, dataFixup($cc, xl('Chief Complaint')));
187 } else if (keySearch($s, '{ReferringDOC}')) {
188 $tmp = empty($ptrow['ur_fname']) ? '' : $ptrow['ur_fname'];
189 if (!empty($ptrow['ur_mname'])) {
190 if ($tmp) {
191 $tmp .= ' ';
194 $tmp .= $ptrow['ur_mname'];
197 if (!empty($ptrow['ur_lname'])) {
198 if ($tmp) {
199 $tmp .= ' ';
202 $tmp .= $ptrow['ur_lname'];
205 $s = keyReplace($s, dataFixup($tmp, xl('Referer')));
206 } else if (keySearch($s, '{Allergies}')) {
207 $tmp = generate_plaintext_field(array('data_type'=>'24','list_id'=>''), '');
208 $s = keyReplace($s, dataFixup($tmp, xl('Allergies')));
209 } else if (keySearch($s, '{Medications}')) {
210 $s = keyReplace($s, dataFixup(getIssues('medication'), xl('Medications')));
211 } else if (keySearch($s, '{ProblemList}')) {
212 $s = keyReplace($s, dataFixup(getIssues('medical_problem'), xl('Problem List')));
213 } // This tag indicates the fields from here until {/GRP} are a group of fields
214 // separated by semicolons. Fields with no data are omitted, and fields with
215 // data are prepended with their field label from the form layout.
216 else if (keySearch($s, '{GRP}')) {
217 ++$groupLevel;
218 $groupCount = 0;
219 $s = keyReplace($s, '');
220 } else if (keySearch($s, '{/GRP}')) {
221 if ($groupLevel > 0) {
222 --$groupLevel;
225 $s = keyReplace($s, '');
226 } // This is how we specify the separator between group items in a way that
227 // is independent of the document format. Whatever is between {ITEMSEP} and
228 // {/ITEMSEP} is the separator string. Default is "; ".
229 else if (preg_match('/^\{ITEMSEP\}(.*?)\{\/ITEMSEP\}/', substr($s, $keyLocation), $matches)) {
230 $itemSeparator = $matches[1];
231 $keyLength = strlen($matches[0]);
232 $s = keyReplace($s, '');
233 } // This handles keys like {LBFxxx:fieldid} for layout-based encounter forms.
234 else if (preg_match('/^\{(LBF\w+):(\w+)\}/', substr($s, $keyLocation), $matches)) {
235 $formname = $matches[1];
236 $fieldid = $matches[2];
237 $keyLength = 3 + strlen($formname) + strlen($fieldid);
238 $data = '';
239 $currvalue = '';
240 $title = '';
241 $frow = sqlQuery(
242 "SELECT * FROM layout_options " .
243 "WHERE form_id = ? AND field_id = ? LIMIT 1",
244 array($formname, $fieldid)
246 if (!empty($frow)) {
247 $ldrow = sqlQuery(
248 "SELECT ld.field_value " .
249 "FROM lbf_data AS ld, forms AS f WHERE " .
250 "f.pid = ? AND f.encounter = ? AND f.formdir = ? AND f.deleted = 0 AND " .
251 "ld.form_id = f.form_id AND ld.field_id = ? " .
252 "ORDER BY f.form_id DESC LIMIT 1",
253 array($pid, $encounter, $formname, $fieldid)
255 if (!empty($ldrow)) {
256 $currvalue = $ldrow['field_value'];
257 $title = $frow['title'];
260 if ($currvalue !== '') {
261 $data = generate_plaintext_field($frow, $currvalue);
265 $s = keyReplace($s, dataFixup($data, $title));
266 } // This handles keys like {DEM:fieldid} and {HIS:fieldid}.
267 else if (preg_match('/^\{(DEM|HIS):(\w+)\}/', substr($s, $keyLocation), $matches)) {
268 $formname = $matches[1];
269 $fieldid = $matches[2];
270 $keyLength = 3 + strlen($formname) + strlen($fieldid);
271 $data = '';
272 $currvalue = '';
273 $title = '';
274 $frow = sqlQuery(
275 "SELECT * FROM layout_options " .
276 "WHERE form_id = ? AND field_id = ? LIMIT 1",
277 array($formname, $fieldid)
279 if (!empty($frow)) {
280 $tmprow = $formname == 'DEM' ? $ptrow : $hisrow;
281 if (isset($tmprow[$fieldid])) {
282 $currvalue = $tmprow[$fieldid];
283 $title = $frow['title'];
286 if ($currvalue !== '') {
287 $data = generate_plaintext_field($frow, $currvalue);
291 $s = keyReplace($s, dataFixup($data, $title));
293 } // End if { character found.
295 return $s;
298 // if (!acl_check('admin', 'super')) die(htmlspecialchars(xl('Not authorized')));
300 // Get patient demographic info.
301 $ptrow = sqlQuery("SELECT pd.*, " .
302 "ur.fname AS ur_fname, ur.mname AS ur_mname, ur.lname AS ur_lname " .
303 "FROM patient_data AS pd " .
304 "LEFT JOIN users AS ur ON ur.id = pd.ref_providerID " .
305 "WHERE pd.pid = ?", array($pid));
307 $hisrow = sqlQuery("SELECT * FROM history_data WHERE pid = ? " .
308 "ORDER BY date DESC LIMIT 1", array($pid));
310 $enrow = array();
312 // Get some info for the currently selected encounter.
313 if ($encounter) {
314 $enrow = sqlQuery("SELECT * FROM form_encounter WHERE pid = ? AND " .
315 "encounter = ?", array($pid, $encounter));
318 $form_filename = strip_escape_custom($_REQUEST['form_filename']);
319 $templatedir = "$OE_SITE_DIR/documents/doctemplates";
320 $templatepath = "$templatedir/$form_filename";
322 // Create a temporary file to hold the output.
323 $fname = tempnam($GLOBALS['temporary_files_dir'], 'OED');
325 // Get mime type in a way that works with old and new PHP releases.
326 $mimetype = 'application/octet-stream';
327 $ext = strtolower(array_pop(explode('.', $filename)));
328 if ('dotx' == $ext) {
329 // PHP does not seem to recognize this type.
330 $mimetype = 'application/msword';
331 } else if (function_exists('finfo_open')) {
332 $finfo = finfo_open(FILEINFO_MIME_TYPE);
333 $mimetype = finfo_file($finfo, $templatepath);
334 finfo_close($finfo);
335 } else if (function_exists('mime_content_type')) {
336 $mimetype = mime_content_type($templatepath);
337 } else {
338 if ('doc' == $ext) {
339 $mimetype = 'application/msword' ;
340 } else if ('dot' == $ext) {
341 $mimetype = 'application/msword' ;
342 } else if ('htm' == $ext) {
343 $mimetype = 'text/html' ;
344 } else if ('html' == $ext) {
345 $mimetype = 'text/html' ;
346 } else if ('odt' == $ext) {
347 $mimetype = 'application/vnd.oasis.opendocument.text' ;
348 } else if ('ods' == $ext) {
349 $mimetype = 'application/vnd.oasis.opendocument.spreadsheet' ;
350 } else if ('ott' == $ext) {
351 $mimetype = 'application/vnd.oasis.opendocument.text' ;
352 } else if ('pdf' == $ext) {
353 $mimetype = 'application/pdf' ;
354 } else if ('ppt' == $ext) {
355 $mimetype = 'application/vnd.ms-powerpoint' ;
356 } else if ('ps' == $ext) {
357 $mimetype = 'application/postscript' ;
358 } else if ('rtf' == $ext) {
359 $mimetype = 'application/rtf' ;
360 } else if ('txt' == $ext) {
361 $mimetype = 'text/plain' ;
362 } else if ('xls' == $ext) {
363 $mimetype = 'application/vnd.ms-excel' ;
367 $zipin = new ZipArchive;
368 if ($zipin->open($templatepath) === true) {
369 // Must be a zip archive.
370 $zipout = new ZipArchive;
371 $zipout->open($fname, ZipArchive::OVERWRITE);
372 for ($i = 0; $i < $zipin->numFiles; ++$i) {
373 $ename = $zipin->getNameIndex($i);
374 $edata = $zipin->getFromIndex($i);
375 $edata = doSubs($edata);
376 $zipout->addFromString($ename, $edata);
379 $zipout->close();
380 $zipin->close();
381 } else {
382 // Not a zip archive.
383 $edata = file_get_contents($templatepath);
384 $edata = doSubs($edata);
385 file_put_contents($fname, $edata);
388 // Compute a download name like "filename_lastname_pid.odt".
389 $pi = pathinfo($form_filename);
390 $dlname = $pi['filename'] . '_' . $ptrow['lname'] . '_' . $pid;
391 if ($pi['extension'] !== '') {
392 $dlname .= '.' . $pi['extension'];
395 header('Content-Description: File Transfer');
396 header('Content-Transfer-Encoding: binary');
397 header('Expires: 0');
398 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
399 header('Pragma: public');
400 // attachment, not inline
401 header("Content-Disposition: attachment; filename=\"$dlname\"");
402 header("Content-Type: $mimetype");
403 header("Content-Length: " . filesize($fname));
404 ob_clean();
405 flush();
406 readfile($fname);
408 unlink($fname);