Edihistory module added.
[openemr.git] / library / edihistory / csv_record_include.php
blobb1a7598ffd9814f858ee6d549b4e43894a47a235
1 <?php
2 /**
3 * csv_record_include.php
4 *
5 * Copyright 2012 Kevin McCormick
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; version 3 or later. You should have
15 * received a copy of the GNU General Public License along with this program;
16 * if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * <http://opensource.org/licenses/gpl-license.php>
20 * @author Kevin McCormick
21 * @link: http://www.open-emr.org
22 * @package OpenEMR
23 * @subpackage ediHistory
27 * The purpose of this file is to hold functions of general utility for
28 * my edi_claim_history project. It began as a php "class" but I am now
29 * thinking that instantiating the class is too much bother and probably
30 * a waste of memory, since the contents of the file have to be read into
31 * memory anyway.
33 * <pre>
34 * ******* important *********
35 * function csv_parameters($type="ALL")
36 * This function must have the correct values or nothing will work
37 * function csv_verify_file( $file_path, $type, $val_array=FALSE )
38 * critical for file verification and x12 parsing
39 * function (in ibr_uploads.php) ibr_upload_match_file($param_ar, $fidx, &$html_str)
40 * contains a regular expression that must be correct
42 * **************************
43 * </pre>
45 * The claim_history x12 files are batch (837) claim status (277 also TA1) and era (835)
46 * (Eligibility status files (271) would probably work in this scheme as well.)
47 * There are also Availity clearinghouse specific files .ibr, .ebr, .dpr (nothing for .dpr files)
49 * <pre>
50 * Basic workflow:
51 * Each file type has a row in the array from csv_paramaters()
52 * type directory files_csv claims_csv column regex
54 * 1. Read the parameters array and choose the parameters using 'type'
55 * 2. Search the 'directory' for files matching the 'regex' regular expressions and
56 * compare the results to the files listed in the 'files_csv' files.csv record -- unmatched files are "new"
57 * 3. Each "new" x12 file should be read by csv_x12_segments -- returns array('path', 'delimiters', 'segments')
58 * ibr, ebr, ack -- basically Availity formats have their own read functions
59 * 4. Pass the array to various functions which parse for claims information
60 * 5. Write the results to files.csv or claims.csv and create html output for display
62 * 6. Other outputs as called for in ibr_history.php -- from user input from claim_history.html
63 * </pre>
65 * Key usability issue is the "new" files are in the users home directory -- downloaded there
66 * while the OpenEMR is on the server -- so there is a basic issue of access to the files
68 * The ibr_uploads.php script handles uploads of zip archives or multiple file uploads
70 * The csv data files are just php written .csv files, so anything different may cause errors
71 * You can open and edit them in OpenOffice, but you must save them in "original format"
73 * TO DO script to read 271 eligibility response files -- add type to csv_verfy_file, csv_parameters, csv_files_header, csv_setup
75 * TO_DO Some type of "find in files" search would be helpful for locating all references to a claim, patient, etc.
76 * [ grep -nHIrF 'findtext']
78 * TO_DO functions to zip old files, put them aside, and remove them from csv tables
79 */
81 ///**
82 // * a security measure to prevent direct web access to this file
83 // */
84 // if (!defined('SITE_IN')) die('Direct access not allowed!');
86 /**
87 * Log messages to the log file
89 * @param string $msg_str the log message
90 * @return int number of characters written
92 function csv_edihist_log ( $msg_str ) {
94 $logfile = csv_edih_basedir();
95 //$logfile = ($logfile) ? $logfile."/edi_history_log.txt" : $GLOBALS['OE_SITE_DIR']."/edi_history_log.txt";
96 $logfile = ($logfile) ? $logfile."/edi_history_log.txt" : false;
97 if (!$logfile) {
98 echo "EDI History log file not available <br />";
99 return false;
101 $rslt = FALSE;
102 if ( is_string($msg_str) ) {
103 $tm = date( 'Ymd:Hms' );
104 $tm .= " " . $msg_str . PHP_EOL;
105 $fh = fopen( $logfile, 'a');
106 if ($fh !== FALSE) {
107 $rslt = fwrite($fh, $tm); // number of characters written
109 fclose($fh);
111 return $rslt;
116 * read the edi_history_log.txt file into an
117 * html formatted ordered list
119 * @return string
121 function csv_log_html() {
122 $html_str = "<div class=\"filetext\">".PHP_EOL."<ol class='logview'>".PHP_EOL;
123 $fp = csv_edih_basedir();
124 $fp = $fp."/edi_history_log.txt";
125 $fh = fopen( $fp, 'r');
126 if ($fh !== FALSE) {
127 while (($buffer = fgets($fh)) !== false) {
128 $html_str .= "<li>$buffer</li>".PHP_EOL;
130 $html_str .= "</ol>".PHP_EOL."</div>".PHP_EOL;
131 if (!feof($fh)) {
132 $html_str .= "<p>Error: unexpected file ending error</p>".PHP_EOL;
134 fclose($fh);
135 } else {
136 $html_str = "<p>Error: unable to open log file</p>".PHP_EOL;
138 return $html_str;
141 function csv_log_archive() {
142 $dte = date('Ymds');
144 $str_out = '';
145 $bdir = csv_edih_basedir();
146 $zname = $bdir.DIRECTORY_SEPARATOR.$dte.'_edihistory_log.txt';
147 $logname = $bdir.DIRECTORY_SEPARATOR.'edi_history_log.txt';
148 $archname = $bdir.DIRECTORY_SEPARATOR.'edihistory_archive.zip';
150 $fs = filesize($logname);
151 if ($fs == false && $fs < 512) {
152 $str_out = "$tm error accessng log file.<br />".PHP_EOL;
153 return $str_out;
154 } elseif ($fs < 512) {
155 $str_out = "$tm log too small to archive.<br />".PHP_EOL;
156 return $str_out;
159 $zip = new ZipArchive;
160 if ($zip->open($archname, ZIPARCHIVE::CREATE | ZIPARCHIVE::CHECKCONS)!==TRUE) {
161 exit("cannot open <$archname>\n");
162 } else {
163 $a = $zip->addFile($logname, $zname);
164 $c = $zip->close();
165 if ($c) {
166 $fh = fopen($logname, "w+b");
167 if ($fh !== FALSE) {
168 $tm = date('Ymd:Hms');
169 $rslt = fwrite($fh, "$tm log file archive created.".PHP_EOL);
171 fclose($fh);
172 $str_out = "$tm log file archive created.<br />".PHP_EOL;
173 } else {
174 $str_out = "$tm error in archive of log file.<br />".PHP_EOL;
177 return $str_out;
181 * open or save a user notes file
183 * @param string
184 * @param bool
185 * @return string
187 function csv_notes_file($content='', $open=true) {
189 $str_html = '';
190 //$fp = dirname(__FILE__) . "/edi_notes.txt";
191 $fp = csv_edih_basedir();
192 $fp = $fp."/edi_notes.txt";
193 if (! is_writable($fp) ) {
194 $fh = fopen( $fp, 'a+b');
195 fclose($fh);
197 if ($open) {
198 // if contents were previously deleted by user and file is empty,
199 // the text 'empty' is put in content in save operation
200 $ftxt = file_get_contents($fp);
201 if ($ftxt === false) { echo "csv_notes_file: file error<br />".PHP_EOL; }
202 if (substr($ftxt, 0, 5) == 'empty' && strlen($ftxt) == 5) {
203 $ftxt = '## '. date("F j, Y, g:i a");
204 } elseif (!$ftxt) {
205 $ftxt = '## '. date("F j, Y, g:i a");
207 $str_html .= $ftxt;
208 } elseif (strlen($content)) {
209 //echo "csv_notes_file: we have content<br />".PHP_EOL;
210 if (preg_match('/[^\x20-\x7E\x0A\x0D]|(<\?)|(<%)|(<asp)|(<ASP)|(#!)|(\$\{)|(<scr)|(<SCR)/', $content, $matches, PREG_OFFSET_CAPTURE)) {
211 $str_html .= "Filtered character in file content not accepted <br />" . PHP_EOL;
212 $str_html .= " character: " . $matches[0][0] . " position: " . $matches[0][1] . "<br />" . PHP_EOL;
213 $saved = false;
214 } else {
215 //echo "csv_notes_file: we are trying to save in $fp<br />".PHP_EOL;
216 $saved = file_put_contents($fp, $content);
218 $str_html .= ($saved) ? "Notes saved<br />" : "Save Error<br />";
219 } else {
220 $ftxt = 'empty';
221 $saved = file_put_contents($fp, $ftxt);
222 $str_html .= ($saved) ? "No content in notes.<br />" : "Save Error with empty file<br />";
225 return $str_html;
229 * set the base path for most file operations
231 * @return string|boolean
233 function csv_edih_basedir() {
234 //$GLOBALS['OE_SITE_DIR']
235 // should be something like /var/www/htdocs/openemr/sites/default
236 if (isset($GLOBALS['OE_SITE_DIR'])) {
237 return $GLOBALS['OE_SITE_DIR'].'/edi/history';
238 } else {
239 csv_edihist_log("csv_edih_basedir: failed to obtain OpenEMR Site directory");
240 return false;
245 * set the temporary directory used for uploads, etc.
247 * @return string
249 function csv_edih_tmpdir() {
250 //define("IBR_UPLOAD_DIR", "/tmp/edihist");
251 $systmp = sys_get_temp_dir();
252 $systmp = stripcslashes($systmp);
253 return $systmp."/edihist";
258 * Initial setup function
260 * Create the directory tree and write the column headers into the csv files
261 * This function will accept a directory argument and it appends the value
262 * from IBR_HISTORY_DIR to the path. Then a directory for each type of file
263 * and the csv files are created under that.
265 * @uses csv_parameters()
266 * @uses csv_files_header()
267 * @param string $dir
268 * @param string &$out_str referenced, should be created in calling function
269 * @return boolean
271 function csv_setup(&$out_str) {
273 $isOK = FALSE;
274 $chr = 0;
275 //$basedir = dirname(__FILE__);
276 $edihist_dir = csv_edih_basedir();
277 if ($edihist_dir) {
278 $basedir = $GLOBALS['OE_SITE_DIR'].DIRECTORY_SEPARATOR.'edi';
279 $csv_dir = $edihist_dir.DIRECTORY_SEPARATOR.'csv';
280 $archive_dir = $edihist_dir.DIRECTORY_SEPARATOR.'archive';
281 } else {
282 //csv_edihist_log("setup: failed to obtain OpenEMR Site directory");
283 $out_str .= "setup: failed to obtain OpenEMR Site directory <br />";
284 return false;
287 if (is_writable($basedir) ) {
288 $isOK = TRUE;
289 $out_str .= "setup: directory $basedir <br />";
290 //csv_edihist_log("setup: directory $basedir");
293 if ($isOK) {
295 if (!mkdir($edihist_dir, 0755, true)) {
296 //csv_edihist_log("Setup: Failed to create folder... $edihist_dir");
297 $out_str .= "Setup: Failed to create folder... $edihist_dir<br />".PHP_EOL;
298 $isOK = FALSE;
299 return false;
300 } else {
301 $p_ar = csv_parameters("ALL");
303 if (!mkdir($csv_dir, 0755, true) ) {
304 $isOK = FALSE;
305 //csv_edihist_log("Setup: Failed to create csv folder...$csv_dir");
306 return false;
308 if (!mkdir($archive_dir, 0755, true) ) {
309 $isOK = FALSE;
310 //csv_edihist_log("Setup: Failed to create archive folder...$archive_dir");
311 return false;
314 foreach ($p_ar as $key=>$val) {
315 // make the file storage subdirs; like /history/era /history/f997, etc.
316 $type_dir = $p_ar[$key]['directory'];
318 if (!is_dir($type_dir) && !mkdir($type_dir, 0755, false) ) {
319 //csv_edihist_log("Setup: failed to create storage directory $key");
320 $out_str .= "Setup: failed to create storage directory $key<br />".PHP_EOL;
321 return false;
323 $out_str .= "created directory for $key<br />" .PHP_EOL;
324 $chr = 0;
326 $hdr_f = csv_files_header($p_ar[$key]['type'], 'file');
327 $hdr_c = csv_files_header($p_ar[$key]['type'], 'claim');
329 $fpath = $p_ar[$key]['files_csv'];
330 $cpath = $p_ar[$key]['claims_csv'];
332 if (is_array($hdr_f) ) {
333 // create the files_type.csv files and insert header row
334 if ($fpath) {
335 //csv_edihist_log("Creating file $fpath for $key");
336 $fh = fopen($fpath, 'x');
337 if ($fh !== FALSE) {
338 $chr = fputcsv($fh, $hdr_f);
339 $out_str .= ($chr) ? "created $fpath <br />" .PHP_EOL : "failed to create $fpath<br />" .PHP_EOL;
340 $isOK = ($chr) ? TRUE : FALSE;
341 $chr = 0;
342 } else {
343 $isOK = FALSE;
344 //csv_edihist_log("Creating file failed for $key");
345 $out_str .= "Creating file failed for $key<br />" .PHP_EOL;
347 fclose($fh);
349 } else {
350 //csv_edihist_log("Did not get header row for $key file");
351 $out_str .= "Did not get header row for $key file<br />".PHP_EOL;
354 if (is_array($hdr_c) ) {
355 // // create the claims_type.csv files and insert header row
356 if ($cpath) {
357 //csv_edihist_log("Creating file $cpath for $key");
358 $fh = fopen($cpath, 'x');
359 if ($fh !== FALSE) {
360 $chr = fputcsv($fh, $hdr_c);
361 $out_str .= ($chr) ? "created $cpath <br />" .PHP_EOL : "failed to write heading row for $cpath<br />" .PHP_EOL;
362 $isOK = ($chr) ? TRUE : FALSE;
363 $chr = 0;
364 } else {
365 $isOK = FALSE;
366 //csv_edihist_log("Creating file failed for $key");
367 $out_str .= "Creating file failed for $key<br />" .PHP_EOL;
369 fclose($fh);
371 } else {
372 $isOK = FALSE;
373 //csv_edihist_log("Did not get header row for $key claims table");
374 $out_str .= "Did not get header row for $key claims table<br />".PHP_EOL;
378 //$GLOBALS['OE_SITE_DIR']."/edi_history_log.txt";
379 //if (is_file($GLOBALS['OE_SITE_DIR']."/edi_history_log.txt")) {
380 // rename ($GLOBALS['OE_SITE_DIR']."/edi_history_log.txt", $edihist_dir."/edi_history_log.txt" );
383 } else {
384 $out_str .= "Setup failed: Can not create directories <br />" . PHP_EOL;
386 return $isOK;
387 //return $out_str;
392 * Empty all contents of tmp dir IBR_UPLOAD_DIR
394 * @param none
395 * @return bool
397 function csv_clear_tmpdir() {
399 $edih_tmpdir = csv_edih_tmpdir();
400 $tmp_files = scandir($edih_tmpdir);
401 if (count($tmp_files)) {
402 foreach($tmp_files as $idx=>$tmpf) {
403 if ($tmpf == "." || $tmpf == "..") {
404 // can't delete . and ..
405 continue;
407 if (is_file($edih_tmpdir.DIRECTORY_SEPARATOR.$tmpf) ) {
408 unlink($edih_tmpdir.DIRECTORY_SEPARATOR.$tmpf);
409 unset($tmp_files[$idx]);
413 if (count($tmp_files) > 2) {
415 csv_edihist_log ( "tmp dir contents remain in $edih_tmpdir");
416 return FALSE;
417 } else {
418 return TRUE;
423 * The array that holds the various parameters used in dealing with files
425 * A key function since it holds the paths, columns, etc. This function relies on
426 * the IBR_HISTORY_DIR constant. Unfortunately, there is an issue with matching the type in
427 * the case of the values '997', '277', '999', etc, becasue these strings may be recast
428 * from strings to integers, so the 'type' originally supplied is lost.
429 * This introduces an inconsistency when the 'type' is used in comparison tests.
430 * The workaround is to say the "type" should have an 'f' prepended to the x12 type number.
431 * The 'datecolumn' and 'fncolumn' entries are used in csv_to_html() to filter by date
432 * or place links to files.
434 * @param string $type -- default = ALL or one of batch, ibr, ebr, dpr, f997, f277, era, ack, text
435 * @return array
437 function csv_parameters($type="ALL") {
439 $edihist_dir = csv_edih_basedir(); // $GLOBALS['OE_SITES_BASE'].'/edi/history'
440 $p_ar = array();
441 // the batch file directory is a special case - decision is to use OpenEMR batch files so users will not have to
442 // upload these. If they are accidentally uploaded, they will be matched and the extra copy will be discarded
443 // OpenEMR copies each batch file to sites/default/edi and this project never reads from or writes to that directory
444 // batch reg ex -- '/20[01][0-9]-[01][0-9]-[0-3][0-9]-[0-9]{4}-batch*\.txt/' '/\d{4}-\d{2}-\d{2}-batch*\.txt$/'
446 //$p_ar['csv'] = array("type"=>'csv', "directory"=>$edihist_dir.'/csv', "claims_csv"=>'ibr_parameters.csv',
447 // "files_csv"=>'', "column"=>'', "regex"=>'/\.csv$/');
449 $p_ar['batch'] = array("type"=>'batch', "directory"=>$GLOBALS['OE_SITE_DIR'].'/edi', "claims_csv"=>$edihist_dir."/csv/claims_batch.csv",
450 "files_csv"=>$edihist_dir."/csv/files_batch.csv", "datecolumn"=>'0', "fncolumn"=>'1', "regex"=>'/\-batch(.*)\.txt$/');
451 $p_ar['ta1'] = array("type"=>'ta1', "directory"=>$edihist_dir.'/f997', "claims_csv"=>'',
452 "files_csv"=>$edihist_dir.'/csv/files_997.csv', "datecolumn"=>'0', "fncolumn"=>'1', "regex"=>'/\.ta1$/i');
453 $p_ar['ack'] = array("type"=>'ack', "directory"=>$edihist_dir.'/f997', "claims_csv"=>'',
454 "files_csv"=>$edihist_dir.'/csv/files_997.csv', "datecolumn"=>'0', "fncolumn"=>'1', "regex"=>'/\.ack$/i');
455 $p_ar['f997'] = array("type"=>'f997', "directory"=>$edihist_dir.'/f997', "claims_csv"=>$edihist_dir.'/csv/claims_997.csv',
456 "files_csv"=>$edihist_dir.'/csv/files_997.csv', "datecolumn"=>'0', "fncolumn"=>'1', "regex"=>'/\.99[79]$/');
458 $p_ar['ibr'] = array("type"=>'ibr', "directory"=>$edihist_dir.'/ibr', "claims_csv"=>$edihist_dir.'/csv/claims_ibr.csv',
459 "files_csv"=>$edihist_dir.'/csv/files_ibr.csv', "datecolumn"=>'0', "fncolumn"=>'1', "regex"=>'/\.ibr$/');
460 $p_ar['ebr'] = array("type"=>'ebr', "directory"=>$edihist_dir.'/ebr', "claims_csv"=>$edihist_dir.'/csv/claims_ebr.csv',
461 "files_csv"=>$edihist_dir.'/csv/files_ebr.csv', "datecolumn"=>'0', "fncolumn"=>'1', "regex"=>'/\.ebr$/');
462 $p_ar['dpr'] = array("type"=>'dpr', "directory"=>$edihist_dir.'/dpr', "claims_csv"=>$edihist_dir.'/csv/claims_dpr.csv',
463 "files_csv"=>'', "datecolumn"=>'1', "fncolumn"=>'5', "regex"=>'/\.dpr$/');
464 $p_ar['f277'] = array("type"=>'f277', "directory"=>$edihist_dir.'/f277', "claims_csv"=>$edihist_dir.'/csv/claims_277.csv',
465 "files_csv"=>$edihist_dir.'/csv/files_277.csv', "datecolumn"=>'0', "fncolumn"=>'1', "regex"=>'/\.277([ei]br)?$/');
466 // OpenEMR stores era files, but the naming scheme is confusing, so we will just use our own directory for them
467 $p_ar['era'] = array("type"=>'era', "directory"=>$edihist_dir.'/era', "claims_csv"=>$edihist_dir.'/csv/claims_era.csv',
468 "files_csv"=>$edihist_dir.'/csv/files_era.csv', "datecolumn"=>'0', "fncolumn"=>'1', "regex"=>'/835[0-9]{5}\.835*|\.(era|ERA)$/');
469 $p_ar['text'] = array("type"=>'text', "directory"=>$edihist_dir.'/text', "claims_csv"=>'',
470 "files_csv"=>'', "column"=>'', "regex"=>'/\.(EB)|(IB)|(DP)|(AC)|(TA)|(99)|(97)T$/i');
472 $tp = strpos('|f837', (string)$type) ? 'batch' : $type;
473 $tp = strpos('|f999', (string)$type) ? 'f997' : $tp;
474 $tp = strpos('|f997', (string)$type) ? 'f997' : $tp;
475 $tp = strpos('|f835', (string)$type) ? 'era' : $tp;
476 $tp = strpos('|f277', (string)$type) ? 'f277' : $tp;
478 if ( array_key_exists($tp, $p_ar) ) {
479 return $p_ar[$tp];
480 } else {
481 return $p_ar;
486 * determine if a csv table has data for select dropdown
488 * @param string default 'json'
489 * @return array json if argument is 'json'
491 function csv_table_select_list($outtp='json') {
492 $optlist = array();
494 $edihist_dir = csv_edih_basedir(); // $GLOBALS['OE_SITE_DIR'].'/edi/history'
495 $thisdir = $edihist_dir.'/csv';
496 $tbllist = scandir($thisdir);
497 $idx = 0;
498 foreach($tbllist as $csvf) {
499 if ($csvf == "." || $csvf == ".." ) { continue; }
500 if (filesize($thisdir.DIRECTORY_SEPARATOR.$csvf) < 90) { continue; }
501 if (substr($csvf, -1) == '~') { continue; }
502 $finfo = pathinfo($thisdir.DIRECTORY_SEPARATOR.$csvf);
503 $fn = $finfo['filename'];
504 $tp = explode('_', $fn);
505 $optlist[$idx]['fname'] = $fn;
506 $optlist[$idx]['desc'] = $tp[1] .' '.$tp[0];
507 $idx++;
509 if ($outtp == 'json') {
510 return json_encode($optlist);
511 } else {
512 return $optlist;
518 * List files in the directory for the given type
520 * Write an entry in the log if an file is in the directory
521 * that does not match the type.
523 * @uses csv_parameters()
524 * @param string $type a type from our list
525 * @return array
527 function csv_dirfile_list ($type) {
528 // return false if location is not appropriate
529 // use regular expressions to select desired files from directory
530 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ack|ta1", $type) ) {
531 if ($type != 'text') {
532 // do not log text type, but do not search either
533 csv_edihist_log("csv_dirfile_list error: incorrect type $type");
535 return FALSE;
537 $params = csv_parameters($type);
538 //$search_dir = dirname(__FILE__).$params['directory'].DIRECTORY_SEPARATOR;
539 $search_dir = $params['directory'].DIRECTORY_SEPARATOR;
540 $typedir = basename($params['directory']);
541 $ext_re = $params['regex'];
542 $dirfiles = array();
544 if (is_dir($search_dir)) {
545 if ($dh = opendir($search_dir)) {
546 while (($file = readdir($dh)) !== false) {
547 if (is_file($search_dir.$file) ) {
548 if (preg_match($ext_re, $file) ) {
549 $dirfiles[] = $file;
550 } elseif ($typedir == 'f997') {
551 $ext = substr($file, -3);
552 // no error, since ack|ta1 files are put there
553 if ($type == 'f997') {
554 if ($ext == 'ack' || $ext == 'ta1') { continue; }
555 } elseif ($type == 'ack') {
556 if (ext == '999' || ext == '997' || $ext == 'ta1') { continue; }
557 } elseif ($type == 'ta1') {
558 if (ext == '999' || ext == '997' || $ext == 'ack') { continue; }
560 } else {
561 csv_edihist_log("csv_dirfile_list: $type wrong type $file");
565 closedir($dh);
567 } else {
568 csv_edihist_log("csv_dirfile_list: Error: $typedir directory seems to be missing!");
571 return $dirfiles;
572 } // end function
576 * List files that are in the csv record
578 * @uses csv_parameters()
579 * @param string $type -- one of our types
580 * @return array
582 function csv_processed_files_list ($type) {
585 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ack|ta1", $type) ) {
586 if ($type != 'text') {
587 csv_edihist_log("csv_processed_files_list error: incorrect type $type");
589 return FALSE;
591 $processed_files = array();
592 $param = csv_parameters($type);
593 $csv_col = $param['fncolumn'];
594 if ($type == 'dpr') {
595 $csv_file = $param['claims_csv'];
596 //$csv_col = '5';
597 } else {
598 $csv_file = $param['files_csv'];
601 $idx = 0;
602 if (($fh1 = fopen( $csv_file, "r" )) !== FALSE) {
603 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
604 if ($idx) { $processed_files[] = $data[$csv_col]; }
605 // skip the header row
606 $idx++;
608 fclose($fh1);
609 } else {
610 csv_edihist_log ("csv_list_processed_files: failed to access $csv_file" );
611 return false;
614 return $processed_files;
615 } // end function
619 * Give an array of files in the storage directories that are not in the csv record
621 * @param string $type -- one of our types
622 * @return array
624 function csv_newfile_list($type) {
626 //f277 f997 ack batch csv ebr era ibr text
627 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ack|ta1", $type) ) {
628 if ($type != 'text') {
629 csv_edihist_log("csv_newfile_list: incorrect type $type");
631 return FALSE;
634 $dir_files = csv_dirfile_list ($type);
635 $csv_files = csv_processed_files_list ($type);
636 // $dir_files should come first in array_diff()
637 if (empty($dir_files)) {
638 // logic error -- fixed; if dir_files is empty, there are no files of that type
639 //$ar_new = $csv_files;
640 $ar_new = $dir_files;
641 } else {
642 $ar_new = array_diff($dir_files, $csv_files);
645 return $ar_new;
650 * Give the column headings for the csv files
652 * @param string $file_type -- one of our types batch|era|ibr|ebr|dpr|f277|f997, etc.
653 * @param string $csv_type -- one of 'file' or 'claim'
654 * @return array
656 function csv_files_header($file_type, $csv_type) {
658 if (! strpos("|era|835|f997|999|ibr|ebr|dpr|f277|batch|837|ta1|ack", $file_type) ) {
659 csv_edihist_log("csv_files_header error: incorrect file type $file_type");
660 return FALSE;
662 if (!strpos('|file|claim', $csv_type) ) {
663 csv_edihist_log("csv_files_header error: incorrect csv type $csv_type");
664 return FALSE;
667 $ft = strpos('|277', $file_type) ? 'f277' : $file_type;
668 $ft = strpos('|835', $file_type) ? 'era' : $ft;
669 $ft = strpos('|837', $file_type) ? 'batch' : $ft;
670 $ft = strpos('|999|997|ack|ta1', $file_type) ? 'f997' : $ft;
672 $csv_hd_ar = array();
673 // actually, 'ack' and 'ta1' are probably redundant, since they are interpreted as 'f997'
674 $csv_hd_ar['ack']['file'] = array('Date', 'FileName', 'isa13', 'ta1ctrl', 'Code');
675 $csv_hd_ar['ebr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
676 $csv_hd_ar['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
678 $csv_hd_ar['batch']['file'] = array('Date', 'FileName', 'Ctn_837', 'claim_ct', 'x12_partner');
679 $csv_hd_ar['ta1']['file'] = array('Date', 'FileName', 'Ctn_ta1', 'ta1ctrl', 'Code');
680 $csv_hd_ar['f997']['file'] = array('Date', 'FileName', 'Ctn_999', 'ta1ctrl', 'RejCt');
681 $csv_hd_ar['f277']['file'] = array('Date', 'FileName', 'Ctn_277', 'Accept', 'AccAmt', 'Reject', 'RejAmt');
682 $csv_hd_ar['era']['file'] = array('Date', 'FileName', 'Trace', 'claim_ct', 'Denied', 'Payer');
684 $csv_hd_ar['ebr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
685 $csv_hd_ar['ibr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
686 $csv_hd_ar['dpr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
688 $csv_hd_ar['batch']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' );
689 $csv_hd_ar['f997']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'File_997', 'Ctn_837', 'err_seg');
690 $csv_hd_ar['f277']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_277', 'File_277', 'payer_name', 'claim_id', 'bht03_837');
691 $csv_hd_ar['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
693 return $csv_hd_ar[$ft][$csv_type];
698 * Determine whether an array is multidimensional
700 * @param array
701 * @return bool false if arrayis multidimensional
703 function csv_singlerecord_test ( $array ) {
704 // the two versions of count() are compared
705 // if the array has a sub-array, count recursive is greater
706 $is_sngl = count($array, COUNT_RECURSIVE ) == count( $array, COUNT_NORMAL);
708 return $is_sngl;
712 * A multidimensional array will be flattened to a single row.
714 * @param array $array array to be flattened
715 * @return array
717 function csv_array_flatten($array) {
719 if (!is_array($array)) {return FALSE;}
720 $result = array();
721 foreach ($array as $key => $value) {
722 if (is_array($value)) {
723 $result = array_merge($result, csv_array_flatten($value));
724 } else {
725 $result[$key] = $value;
728 return $result;
732 * Append rows to one of the csv record files.
734 * @uses csv_singlerecord_test()
735 * @uses csv_parameters()
736 * @uses csv_files_header()
737 * @param array $csv_data the data array, either file data or claim data
738 * @param string $file_type which of our file types to use
739 * @param string $csv_type either 'claim' or 'file'
740 * @return int number of characters written per fputcsv()
742 function csv_write_record($csv_data, $file_type, $csv_type) {
744 if (!is_array($csv_data)) { return FALSE;}
745 // use CSV_RECORD class to write ibr or ebr claims data to the csv file
746 // csv, batch, ibr, ebr, f997, or era
747 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ack", $file_type) ) {
748 csv_edihist_log("csv_write_record error: incorrect file type $file_type");
749 return FALSE;
752 $ft = $file_type;
753 $ft = strpos("|835", $file_type) ? 'era' : $ft;
754 $ft = strpos("|837", $file_type) ? 'batch' : $ft;
755 $ft = strpos("|999|ack|ta1", $file_type) ? 'f997' : $ft;
757 $params = csv_parameters($ft);
759 if ($csv_type == "claim") {
760 $fp = $params['claims_csv'];
761 } elseif ($csv_type == "file") {
762 $fp = $params['files_csv'];
763 } else {
764 csv_edihist_log("csv_writedata_csv error: incorrect csv type $csv_type");
765 return FALSE;
768 $fh = fopen( $fp, 'a');
769 // count characters written -- returned by fputcsv
770 $indc = 0;
771 // if we fail to open the file, return the result, expect FALSE
772 if (!$fh) { return FALSE; }
773 // test for a new file
774 if ( filesize($fp) === 0 ) {
775 $ar_h = csv_files_header($file_type, $csv_type);
776 $td = fgetcsv($fh);
777 if ($td === FALSE || $td === NULL ) {
778 // assume we have an empty file
779 // write header row if this is a new csv file
780 if (count($ar_h) ) {
781 $indc += fputcsv ( $fh, $ar_h );
786 // test array for dimension counts
787 $is_sngl = csv_singlerecord_test($csv_data) ;
788 if ( $is_sngl ) {
789 $indc += fputcsv ( $fh, $csv_data );
790 } else {
791 // multi-dimensional array -- we rely on array_flatten to
792 // assure us that the array depth is 1
793 foreach ($csv_data as $row) {
794 $wr = csv_array_flatten($row);
795 // $wr is false if $row is not an array
796 if ($wr) {
797 $indc += fputcsv ( $fh , $wr );
798 } else {
799 continue;
803 fclose($fh);
805 return $indc;
809 * Search a csv record file and return the row or values from selected columns
811 * This function requires that the $search_ar parameter be an array
812 * with keys ['s_val']['s_col']['r_cols'], and 'r_cols' is an array
813 * 's_val' is the search value, s_col is the column to check, r_cols is an array
814 * of column numbers from which values are returned. If r_cols is not an array,
815 * then the entire row will be returned. If the 'expect' parameter is 1, then
816 * the search will stop after the first success and return the result. Otherwise, the
817 * entire file will be searched.
818 * ex: csv_search_record('batch', 'claim', array('s_val'=>'20120115', 's_col'=>1, 'r_cols'=>array(0, 1, 2, 8)), "2" )
820 * @uses csv_parameters()
821 * @param string $file_type
822 * @param string $csv_type
823 * @param array $search_ar
824 * @param mixed $expect
825 * @return array
827 function csv_search_record($file_type, $csv_type, $search_ar, $expect="1") {
829 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ack", $file_type) ) {
830 csv_edihist_log("csv_search_record: incorrect file type $file_type");
831 return FALSE;
834 $params = csv_parameters($file_type);
836 if ($csv_type == "claim") {
837 $fp = $params['claims_csv'];
838 } elseif ($csv_type == "file") {
839 $fp = $params['files_csv'];
840 } else {
841 csv_edihist_log("csv_search_record: incorrect csv type $csv_type");
842 return FALSE;
845 if (!is_array($search_ar) || array_keys($search_ar) != array('s_val', 's_col', 'r_cols')) {
846 csv_edihist_log("csv_search_record: invalid search criteria");
847 return FALSE;
849 $sv = $search_ar['s_val'];
850 $sc = $search_ar['s_col'];
851 $rv = (is_array($search_ar['r_cols']) && count($search_ar['r_cols'])) ? $search_ar['r_cols'] : 'all';
852 $ret_ar = array();
853 $idx = 0;
855 if (($fh1 = fopen($fp, "r")) !== FALSE) {
856 while (($data = fgetcsv($fh1)) !== FALSE) {
857 // check for a match
858 if ($data[$sc] == $sv) {
859 if ($rv == 'all') {
860 $ret_ar[$idx] = $data;
861 } else {
862 // now loop through the 'r_cols' array for data index
863 $dct = count($data);
864 foreach($rv as $c) {
865 // make sure we don't access a non-existing index
866 if ($c >= $dct) { continue; }
868 $ret_ar[$idx][] = $data[$c];
871 $idx++;
872 if ($expect == '1') { break; }
875 fclose($fh1);
876 } else {
877 csv_edihist_log("csv_search_record: failed to open $fp");
878 return false;
880 if (empty($ret_ar) ) {
881 return false;
882 } else {
883 return $ret_ar;
888 * Search the 'claims' csv table for the patient control and find the associated file name
890 * In 'claims' csv tables, clm01 is position 2 number is pos 4, and filename is pos 5;
891 * except in ebr, ibr, and dpr files which have batch name in pos 4. See the
892 * csv files column headings for more information.
894 * @uses csv_parameters()
895 * @uses csv_pid_enctr_parse()
896 * @see csv_files_header()
897 * @param string patient control-- pid-encounter, pid, or encounter
898 * @param string filetype batch, era, f277, f997, ibr, ebr, dpr
899 * @param string search type encounter, pid, or ptctln
900 * @return array|bool [i](pid_encounter, number, filename) or false on error
902 function csv_file_with_pid_enctr ($ptctln, $filetype='batch', $srchtype='encounter' ) {
904 // return array of [i](pid_encounter, filename), there may be more than one file
906 if (!$ptctln) {
907 csv_edihist_log("csv_file_with_pid_enctr: missing encounter data");
908 //return "invalid encounter data<br />" . PHP_EOL;
909 return false;
911 // IBR_FTYPES
912 if (! strpos('|era|835|f997|999|ibr|ebr|dpr|f277|batch|837|ta1|ack', $filetype) ) {
913 csv_edihist_log("csv_file_with_pid_enctr: incorrect file type $filetype");
914 return false;
915 } else {
916 $params = csv_parameters($filetype);
917 //$fp = isset($params['claims_csv']) ? dirname(__FILE__).$params['claims_csv'] : false;
918 $fp = isset($params['claims_csv']) ? $params['claims_csv'] : false;
919 if (!$fp) {
920 csv_edihist_log("csv_file_with_pid_enctr: incorrect file type $filetype");
921 return false;
925 $enctr = trim($ptctln);
927 preg_match('/\D/', $enctr, $match2, PREG_OFFSET_CAPTURE);
929 if (count($match2)) {
931 if ($srchtype != 'ptctln') {
932 $idar = csv_pid_enctr_parse($enctr);
933 if (is_array($idar) && count($idar)) {
934 $p = strval($idar['pid']);
935 $plen = strlen($p);
936 $e = strval($idar['enctr']);
937 $elen = strlen($e);
938 } else {
939 csv_edihist_log("csv_file_with_pid_enctr: error parsing pid_encounter $pid_enctr");
940 return false;
942 } else {
943 $pe = $enctr;
945 } else {
946 // no match from preg_match, so $enctr has no non-digit characer like '-'
947 if ($srchtype == 'ptctln') {
948 if (strlen($enctr) > IBR_ENCOUNTER_DIGIT_LENGTH) {
949 $pe = substr($enctr, 0, strlen($enctr)-IBR_ENCOUNTER_DIGIT_LENGTH) .'-'.substr($enctr, -IBR_ENCOUNTER_DIGIT_LENGTH);
950 } else {
951 // no pid, so change search type to encounter only
952 $srchtype = 'encounter';
955 $p = strval($enctr);
956 $e = strval($enctr);
957 $plen = strlen($p);
958 $elen = strlen($e);
961 $ret_ar = array();
962 // in 'claims' csv tables, clm01 is position 2 and filename is position 5
963 if (($fh1 = fopen($fp, "r")) !== FALSE) {
964 if ($srchtype == 'encounter') {
965 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
966 // check for a match
967 if (substr($data[2], -$elen) == $e) {
968 // since e=123 will match 1123 and 123
969 $peval = csv_pid_enctr_parse($data[2]);
970 if (is_array($peval) && count($peval)) {
971 if ($peval['enctr'] == $e) {
972 $ret_ar[] = array($data[2], $data[4], $data[5]);
977 } elseif ($srchtype == 'pid') {
978 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
979 // check for a match
980 if (substr($data[2], 0, $plen) == $p) {
981 // since p=123 will match 1123 and 123
982 $peval = csv_pid_enctr_parse($data[2]);
983 if (is_array($peval) && count($peval)) {
984 if ($peval['pid'] == $p) {
985 $ret_ar[] = array($data[2], $data[4], $data[5]);
990 } else {
991 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
992 // check for a match
993 if ($data[2] == $pe) {
994 $ret_ar[] = array($data[2], $data[4], $data[5]);
998 fclose($fh1);
999 } else {
1000 csv_edihist_log("csv_file_with_pid_enctr: failed to open csv file ");
1001 return false;
1003 return $ret_ar;
1007 * get the x12 file containing the control_num ISA13
1009 * The csv for x12 files 999, 277, 835, 837 has the control number in pos 2
1010 * and the filename in pos 1. This is a convenience function, since the actual
1011 * work is done by csv_search_record()
1013 * @uses csv_search_record()
1014 * @param string $control_num the interchange control number, isa13
1015 * @return string the file name
1017 function csv_file_by_controlnum($type, $control_num) {
1018 // get the batch file containing the control_num
1020 if (! strpos("|era|f997|f277|batch|ta1", $type) ) {
1021 csv_edihist_log("csv_file_by_controlnum: incorrect file type $type");
1022 return FALSE;
1024 // $search_ar should have keys ['s_val']['s_col'] array(['r_cols'][])
1025 // like "batch', 'claim, array(9, '0024', array(1, 2, 7))
1026 $fn = '';
1027 if ($type == 'era') {
1028 $ctln = trim(strval($control_num));
1029 } else {
1030 $ctln = (strlen($control_num) >= 9) ? substr($control_num, 0, 9) : trim(strval($control_num));
1032 $search = array('s_val'=>$ctln, 's_col'=>2, 'r_cols'=>array(1));
1033 $result = csv_search_record($type, 'file', $search, "1");
1034 if (is_array($result) && count($result[0]) == 1) {
1035 $fn = $result[0][0];
1037 return $fn;
1041 * A function to try and assure the pid-encounter is correctly parsed
1043 * assume a format of pid-encounter, since that is sent in the OpenEMR x12 837
1044 * however, in case payer mangles the pid-encounter by dropping the separator,
1045 * check value and use IBR_ENCOUNTER_DIGIT_LENGTH constant
1047 * @param string $pid_enctr the value from element CPL01
1048 * return array array('pid' => $pid, 'enctr' => $enc)
1050 function csv_pid_enctr_parse( $pid_enctr ) {
1051 // evaluate the patient account field
1053 if (!$pid_enctr || !is_string($pid_enctr) ) {
1054 csv_edihist_log("csv_pid_enctr_parse: invalid argument");
1055 return false;
1057 $pval = trim($pid_enctr);
1058 preg_match('/\D/', $pval, $match2, PREG_OFFSET_CAPTURE);
1059 $inv_split = (count($match2)) ? preg_split('/\D/', $pval, 2, PREG_SPLIT_NO_EMPTY) : false;
1060 if ($inv_split) {
1061 $pid = $inv_split[0];
1062 $enc = $inv_split[1];
1063 } elseif ( preg_match('/20[01]{1}[0-9]{1}(0[0-9]{1}|1[0-2]{1})[0-3]{1}[0-9]{1}/', $pval) ) {
1064 // encounter numbers can also be Ymd like 20110412
1065 $enc = $pval;
1066 $pid = '';
1067 } else {
1068 $enc = (strlen($pval) >= IBR_ENCOUNTER_DIGIT_LENGTH) ? substr($pval, -IBR_ENCOUNTER_DIGIT_LENGTH) : $pval;
1069 $pid = (strlen($pval) > IBR_ENCOUNTER_DIGIT_LENGTH) ? substr($pval, 0, (strlen($pval)-IBR_ENCOUNTER_DIGIT_LENGTH)) : '';
1071 return array('pid' => $pid, 'enctr' => $enc);
1076 * This function is supposed to allow the downloading of a file.
1078 * Not used or tested -- do not use. Since the users cannot scan the directories,
1079 * the file to be downloaded would have to be selected from a csv table display
1080 * or some other listing produced by reading the directories.
1082 * @todo implement this function
1083 * @param string $filename
1084 * @return void --save file dialogue
1086 function csv_download_file( $filename ){
1087 // adapted from http://php.net/manual/en/function.header.php
1088 // phpnet at holodyn dot com 31-Jan-2011 01:01
1089 // Must be fresh start
1090 // ///////////////////// this function not used as of now and probably doesn't work
1091 //////////////////////////////////////////////////////////////////////////////////////
1092 // but a "view file" function will be made, probably as a separate page
1093 // links in csv file
1094 // <a href='edi_view_file.php?key=filename' target='_blank'>filename</a>
1095 // OpenEMR open log link: <a href='../../library/freeb/process_bills.log' target='_blank' class='link_submit' title=''>[View Log]</a>
1097 if( headers_sent() ) {
1098 csv_edihist_log("csv_download_file: error headers already sent");
1099 return FALSE;
1101 //FILTER_SANITIZE_URL
1102 //$filename = $_GET['dlkey'];
1104 $filename = filter_input(INPUT_GET,'dlkey',FILTER_SANITIZE_STRING);
1105 $fp = csv_check_filepath($filename);
1106 if (!fp) {
1107 csv_edihist_log("csv_download_file: invalid filename for download $filename");
1108 //echo "csv_download_file: invalid filename for download $filename <br />" . PHP_EOL;
1109 return FALSE; // no -- httpd error code 504
1112 $file_html = csv_filetohtml($fp);
1114 if ($file_html) {
1115 $bn = basename($filename) . '.html';
1116 $ctype="text/html";
1117 $host = $_SERVER['HTTP_HOST'];
1118 $uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
1119 header("Location: http://$host$uri/$extra");
1120 file_put_contents($host . $uri .DIRECTROY_SEPARATOR. $bn, $file_html);
1121 $fsize = filesize($host . $uri .DIRECTROY_SEPARATOR. $bn);
1124 } else {
1125 csv_edihist_log("csv_download_file: file was not converted to html $filename");
1126 //echo "csv_download_file: file was not converted to html $filename <br />" . PHP_EOL;
1127 return FALSE;
1130 // Required for some browsers
1131 if(ini_get('zlib.output_compression'))
1132 ini_set('zlib.output_compression', 'Off');
1135 $ctype="application/pdf";
1137 header("Pragma: public"); // required
1138 header("Expires: 0");
1139 header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
1140 header("Cache-Control: private",false); // required for certain browsers
1141 header("Content-Type: $ctype");
1142 header("Content-Disposition: attachment; filename=\"".basename($fp)."\";" );
1143 header("Content-Transfer-Encoding: binary");
1144 header("Content-Length: ".$fsize);
1145 ob_clean();
1146 flush();
1147 readfile( $fp );
1149 exit();
1155 * check the csv claims tables and return rows for a particular encounter
1157 * @uses csv_pid_enctr_parse()
1158 * @uses csv_file_with_pid_enctr()
1159 * @uses csv_table_select_list()
1160 * @uses csv_search_record()
1161 * @param string encounter number
1162 * @return string
1164 function csv_claim_history($encounter) {
1166 if ($encounter) {
1167 $enct = csv_pid_enctr_parse(strval($encounter));
1168 $e = ($enct) ? $enct['enctr'] : false;
1171 if (!$e) {
1172 return "invalid encounter value $encounter <br />".PHP_EOL;
1174 // get complete pid-encounter from the batch claims record
1175 $efp = csv_file_with_pid_enctr($e);
1176 if (is_array($efp) && count($efp)) {
1177 $pe = $efp[0][0];
1178 } else {
1179 csv_edihist_log("csv_claim_history: failed to locate $e in batch claims record");
1180 return "failed to locate $e in batch claims record";
1182 // use function csv_table_select_list() so that only
1183 // existing csv tables are queried
1184 $tbl2 = csv_table_select_list('array');
1185 $rtypes = array();
1186 if (is_array($tbl2) && count($tbl2) ) {
1187 foreach($tbl2 as $tbl) {
1188 $tp1 = explode(' ', $tbl['desc']);
1189 if ($tp1[1] == 'files') { continue; }
1190 if ($tp1[0] == '999' || $tp1[0] == '997' || $tp1[0] == '277') {
1191 $k = 'f'.$tp1[0];
1192 $rtypes[$k] = $k;
1193 } elseif ($tp1[0] == 'ibr' || $tp1[0] == 'ebr' || $tp1[0] == 'dpr') {
1194 $k = $tp1[0];
1195 $rtypes['prop'][] = $k;
1196 } else {
1197 $k = $tp1[0];
1198 $rtypes[$k] = $k;
1201 } else {
1202 csv_edihist_log("csv_claim_history: failed to get csv table names");
1203 return "failed to get csv table names";
1206 $ch_html .= "<table class='clmhist' columns=4><caption>Encounter Record for $pe</caption>";
1207 $ch_html .= "<tbody>".PHP_EOL;
1209 if (isset($rtypes['batch'])) {
1210 $tp = 'batch';
1211 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1212 $btar = csv_search_record($tp, 'claim', $srchar, '2');
1214 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1215 $ch_html .= "<td>Name</td><td>SvcDate</td><td>CLM01</td><td>File</td>".PHP_EOL;
1216 $ch_html .= "</tr>".PHP_EOL;
1217 if (is_array($btar) && count($btar)) {
1218 foreach($btar as $ch) {
1219 $dt = substr($ch[1], 0, 4).'-'.substr($ch[1], 4, 2).'-'.substr($ch[1], 6, 2);
1220 //array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' );
1221 $ch_html .= "<tr class='chbatch'>".PHP_EOL;
1223 $ch_html .= "<td>{$ch[0]}</td>".PHP_EOL;
1224 $ch_html .= "<td>$dt</td>".PHP_EOL;
1225 $ch_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch={$ch[5]}&btpid={$ch[2]}'>{$ch[2]}</a></td>".PHP_EOL;
1226 $ch_html .= "<td title='{$ch[4]}'><a target='_blank' href='edi_history_main.php?fvkey={$ch[5]}'>{$ch[5]}</a></td>".PHP_EOL;
1228 $ch_html .= "</tr>".PHP_EOL;
1230 } else {
1231 $ch_html .= "<tr class='chbatch'>".PHP_EOL;
1232 $ch_html .= "<td colspan=4>Batch -- Nothing found for $pe in $tp record</td>".PHP_EOL;
1233 $ch_html .= "</tr>".PHP_EOL;
1237 if (isset($rtypes['f997'])) {
1238 $tp = 'f997';
1239 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1240 $f997ar = csv_search_record($tp, 'claim', $srchar, '2');
1242 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1243 $ch_html .= "<td>Response</td><td>Status</td><td>File</td><td>Notes</td>".PHP_EOL;
1244 $ch_html .= "</tr>".PHP_EOL;
1245 if (is_array($f997ar) && count($f997ar)) {
1246 foreach($f997ar as $ch) {
1248 $msg = strlen($ch[7]) ? $ch[7] : 'ST Number';
1249 //array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'File_997', 'Ctn_837', 'err_seg');
1250 $ch_html .= "<tr class='chf997'>";
1251 $ch_html .= "<td>997/999</td>".PHP_EOL;
1252 $ch_html .= "<td><a class='clmstatus' target='_blank' href='edi_history_main.php?fv997={$ch[5]}&aknum={$ch[4]}'>{$ch[3]}</a></td>".PHP_EOL;
1253 $ch_html .= "<td><a target='_blank' href='edi_history_main.php?fvkey={$ch[5]}'>{$ch[5]}</a></td>".PHP_EOL;
1254 $ch_html .= "<td title='$msg'>{$ch[6]} {$ch[4]}</td>".PHP_EOL;
1255 $ch_html .= "</tr>".PHP_EOL;
1257 } else {
1258 $ch_html .= "<tr class='chf997'>";
1259 $ch_html .= "<td colspan=4>x12 999 -- Nothing found for $pe</td>".PHP_EOL;
1260 $ch_html .= "</tr>".PHP_EOL;
1264 if (isset($rtypes['f277'])) {
1265 $tp = 'f277';
1267 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1268 $f277ar = csv_search_record($tp, 'claim', $srchar, '2');
1270 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1271 $ch_html .= "<td>Response</td><td>Status</td><td>File</td><td>ClaimID</td>".PHP_EOL;
1272 $ch_html .= "</tr>".PHP_EOL;
1273 if (is_array($f277ar) && count($f277ar)) {
1274 foreach($f277ar as $ch) {
1275 // array('PtName', 'SvcDate', 'clm01', 'Status', 'st_277', 'File_277', 'payer_name', 'claim_id', 'bht03_837');
1276 $ch_html .= "<tr class='chf277'>";
1278 $ch_html .= "<td>x12 277</td>".PHP_EOL;
1279 $ch_html .= "<td><a class='clmstatus' target='_blank' href='edi_history_main.php?rspfile={$ch[5]}&pidenc={$ch[2]}&rspstnum={$ch[4]}'>{$ch[3]}</a></td>".PHP_EOL;
1280 $ch_html .= "<td title='{$ch[5]}'><a target='_blank' href='edi_history_main.php?fvkey={$ch[5]}'>File</a></td>".PHP_EOL;
1281 $ch_html .= "<td title='{$ch[6]}'>{$ch[7]}</td>".PHP_EOL;
1283 $ch_html .= "</tr>".PHP_EOL;
1285 } else {
1286 $ch_html .= "<tr class='chf277'>";
1287 $ch_html .= "<td colspan=4>x12 277 -- Nothing found for $pe</td>".PHP_EOL;
1288 $ch_html .= "</tr>".PHP_EOL;
1292 if (is_array($rtypes['prop']) && count($rtypes['prop']) ) {
1293 foreach($rtypes['prop'] as $tp) {
1295 $rspnm = strtoupper($tp);
1296 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1297 $ibrar = csv_search_record($tp, 'claim', $srchar, '2');
1299 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1300 $ch_html .= "<td>Response</td><td>Status</td><td>File</td><td>Payer</td>".PHP_EOL;
1301 $ch_html .= "</tr>".PHP_EOL;
1302 if (is_array($ibrar) && count($ibrar)) {
1303 foreach($ibrar as $ch) {
1304 //array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1305 $ch_html .= "<tr class='ch$tp'>";
1307 $ch_html .= "<td>$rspnm</td>".PHP_EOL;
1308 if ($tp == 'dpr') {
1309 $ch_html .= "<td><a class='clmstatus' target='_blank' href='edi_history_main.php?dprfile={$ch[5]}&dprclm={$ch[2]}'>{$ch[3]}</a></td>".PHP_EOL;
1310 } else {
1311 $ch_html .= "<td><a class='clmstatus' target='_blank' href='edi_history_main.php?ebrfile={$ch[5]}&ebrclm={$ch[2]}&batchfile={$ch[4]}'>{$ch[3]}</a></td>".PHP_EOL;
1313 $ch_html .= "<td title='{$ch[5]}'><a target='_blank' href='edi_history_main.php?fvkey={$ch[5]}'>File</a></td>".PHP_EOL;
1314 $ch_html .= "<td>{$ch[6]}</td>".PHP_EOL;
1316 $ch_html .= "</tr>".PHP_EOL;
1318 } else {
1319 $ch_html .= "<tr class='ch$tp'>";
1320 $ch_html .= "<td colspan=4>$rspnm -- Nothing found for $pe</td>".PHP_EOL;
1321 $ch_html .= "</tr>".PHP_EOL;
1326 if (isset($rtypes['era'])) {
1327 $tp = 'era';
1329 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1330 $eraar = csv_search_record($tp, 'claim', $srchar, '2');
1332 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1333 $ch_html .= "<td>Response</td><td>Status</td><td>Trace</td><td>Payer</td>".PHP_EOL;
1334 $ch_html .= "</tr>".PHP_EOL;
1335 if (is_array($eraar) && count($eraar)) {
1336 foreach($eraar as $ch) {
1338 $msg = $ch[6] .' '.$ch[7].' '.$ch[8];
1339 // array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
1340 $ch_html .= "<tr class='ch835'>";
1342 $ch_html .= "<td>x12 ERA</td>".PHP_EOL;
1343 $ch_html .= "<td>{$ch[3]} <a class='clmstatus' target='_blank' href='edi_history_main.php?erafn={$ch[5]}&pidenc={$ch[2]}&summary=yes'>S</a>&nbsp;&nbsp;<a target='_blank' href='edi_history_main.php?erafn={$ch[5]}&pidenc={$ch[2]}&srchtp=encounter'>RA</a></td>".PHP_EOL;
1344 $ch_html .= "<td><a target='_blank' href='edi_history_main.php?erafn={$ch[5]}&trace={$ch[4]}&srchtp=trace'>{$ch[4]}</a>&nbsp;&nbsp;<a target='_blank' href='edi_history_main.php?fvkey={$ch[5]}'>x12</a></td>".PHP_EOL;
1345 $ch_html .= "<td title=$msg>{$ch[9]}</td>".PHP_EOL;
1347 $ch_html .= "</tr>".PHP_EOL;
1349 } else {
1350 $ch_html .= "<tr class='ch835'>";
1351 $ch_html .= "<td colspan=4>x12 835 ERA -- Nothing found for $pe</td>".PHP_EOL;
1352 $ch_html .= "</tr>".PHP_EOL;
1355 } // end if($tp ...
1356 // -- this is where a query on the payments datatable could be used to show if payment
1357 // has been received, even if no era file shows the payment.
1359 $ch_html .= "</tbody>".PHP_EOL;
1360 $ch_html .= "</table>".PHP_EOL;
1362 return $ch_html;
1367 * Render one of our csv record files as an html table
1369 * This function determines the actual csv file from the file_type and the
1370 * csv_type. A percentage (default=100) of the csv file rows is selected
1371 * or the date field of each row is checked against the optional date parameters.
1373 * @uses csv_parameters()
1374 * @param string $file_type -- one of "|era|f997|ibr|ebr|dpr|f277|batch"
1375 * @param string $csv_type -- either "file" or "claim"
1376 * @param float $row_pct the percentage of the table to return (most recent)
1377 * @param string $datestart
1378 * @param string $dateend
1379 * @return string
1381 function csv_to_html($file_type, $csv_type, $row_pct = 1, $datestart='', $dateend='') {
1383 // read a csv file into an html table, using predefined stylesheet and javascript
1385 $csv_html = "";
1386 $is_date = FALSE;
1387 // debug
1388 //echo "csv_to_html: $file_type $csv_type $row_pct $datestart $dateend <br />" .PHP_EOL;
1389 //<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch={$val['batch']}&btpid={$val['pid']}'>{$val['pid']}</td>
1390 // <td><a class='clmstatus' target='_blank' href='edi_history_main.php?rspfile={$val['file_277']}&pidenc={$val['pid']}&rspstnum={$val['st_277']}'>{$val['status']}</td>
1392 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ack|ta1", $file_type) ) {
1393 csv_edihist_log("csv_to_html error: incorrect file type $file_type");
1394 $csv_html .= "csv_to_html error: incorrect file type $file_type <br />".PHP_EOL;
1395 return FALSE;
1398 $params = csv_parameters($file_type);
1400 // csv tables date is given date or mtime in col 0 for file, date is service date in col 1 for claim
1401 $dtcol = ($csv_type == "file") ? $params['datecolumn'] : '1';
1402 $fncol = ($csv_type == "file") ? $params['fncolumn'] : '1';
1403 // but dpr files are only in the claims table, a special case
1404 if ($file_type == "dpr") { $fncol = $params['fncolumn']; }
1406 if ($csv_type == "claim") {
1407 //$fp = dirname(__FILE__).$params['claims_csv'];
1408 $fp = $params['claims_csv'];
1409 } elseif ($csv_type == "file") {
1410 //$fp = dirname(__FILE__).$params['files_csv'];
1411 $fp = $params['files_csv'];
1412 } else {
1413 csv_edihist_log("csv_to_html error: incorrect csv type $csv_type");
1414 $csv_html .= "csv_to_html error: incorrect csv type $csv_type <br />".PHP_EOL;
1415 return FALSE;
1417 // for using OpenEMR dynarch calendar, assume format of CCYY-MM-DD
1418 if (preg_match('/\d{4}-\d{2}-\d{2}/', $datestart) && preg_match('/\d{4}-\d{2}-\d{2}/', $dateend) ) {
1419 $ds = str_replace('-', '', $datestart);
1420 $de = str_replace('-', '', $dateend);
1421 if ( $de <= $ds) { $de = date("Ymd", time()); }
1422 $is_date = TRUE;
1423 $row_pct = 1;
1425 /* comment datepicker format dates
1426 if (preg_match('/\d{2}\/\d{2}\/\d{4}/', $datestart) && preg_match('/\d{2}\/\d{2}\/\d{4}/', $dateend) ) {
1427 // we want a date range -- need a better regex
1428 // assume submit format /mm/dd/yyyy -- set in edih_view.php datepicker javascript
1429 $d1 = explode( "/", $datestart);
1430 $ds = $d1[2] . $d1[0] . $d1[1];
1431 $d1 = explode( "/", $dateend);
1432 $de = $d1[2] . $d1[0] . $d1[1];
1433 $is_date = TRUE;
1434 $row_pct = 1;
1437 * end comment datepicker format dates
1440 $f_name = basename($fp);
1441 // open the file for read and read it into an array
1442 $fh = fopen($fp, "r");
1443 if ($is_date) {
1444 $isok = FALSE;
1445 $idx = 0;
1447 if ($fh !== FALSE) {
1448 while (($data = fgetcsv($fh, 1024, ",")) !== FALSE) {
1450 if ($idx == 0) {
1451 $csv_d[] = $data;
1452 } else {
1453 $isok = (substr($data[$dtcol], 0, 8) >= $ds) ? TRUE : FALSE;
1454 $isok = (substr($data[$dtcol], 0, 8) > $de) ? FALSE : $isok;
1456 if ($isok) { $csv_d[] = $data; }
1458 $idx++;
1460 fclose($fh);
1461 } else {
1462 $csv_html .= "csv_to_html: failed to open $fp <br />".PHP_EOL;
1463 return $csv_html;
1465 } else {
1466 // get the entire table
1467 if ($fh !== FALSE) {
1468 while (($data = fgetcsv($fh)) !== FALSE) {
1469 $csv_d[] = $data;
1471 fclose($fh);
1472 } else {
1473 $csv_html .= "csv_to_html: failed to open $fp <br />".PHP_EOL;
1474 return $csv_html;
1478 $ln_ct = count($csv_d);
1479 // make sure row_pct is between 0 and 1
1480 if ($row_pct > 1 || $row_pct <= 0) { $row_pct = 1; }
1481 // only return the number of desired rows
1482 $rwct = (int)($ln_ct * $row_pct) + 1;
1483 $rwst = $ln_ct - $rwct;
1484 if ($rwst < 1) { $rwst = 1; $rwct = $ln_ct; }
1486 if ($is_date) {
1487 $csv_html .= "<h4>Table: $f_name &nbsp;&nbsp; Start Date: $datestart &nbsp; End Date: $dateend &nbsp;Rows: $rwct</h4>".PHP_EOL;
1488 } else {
1489 $csv_html .= "<h4>Table: $f_name &nbsp;&nbsp; Rows: $ln_ct &nbsp;&nbsp; Shown: $rwct</h4>".PHP_EOL;
1492 $csv_html .= "<table id=\"csvTable\" class=\"csvDisplay\">".PHP_EOL;
1493 // this is the body of the table
1495 if ($csv_type == 'file') {
1496 //['era']['file'] = array('Date', 'FileName', 'Trace', 'claim_ct', 'Denied', 'Payer');
1498 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1499 foreach ($csv_d[0] as $h) { $csv_html .= "<th>$h</th>"; }
1500 $csv_html .= PHP_EOL.'</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1502 if ($file_type == 'era') {
1503 for ($i=$rwst; $i<$ln_ct; $i++) {
1504 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1505 $csv_html .= "<tr class='{$bgc}'>".PHP_EOL;
1506 foreach($csv_d[$i] as $idx=>$dta) {
1507 if ($idx == 1) {
1508 $csv_html .= "<td><a href='edi_history_main.php?fvkey=$dta' target='_blank'>$dta</a></td>".PHP_EOL;
1509 } elseif ($idx == 2) {
1510 $fnm = $csv_d[$i][1];
1511 $csv_html .= "<td><a href='edi_history_main.php?erafn=$fnm&trace=$dta' target='_blank'>$dta</a></td>".PHP_EOL;
1512 } else {
1513 $csv_html .= "<td>$dta</td>".PHP_EOL;
1516 $csv_html .= "</tr>".PHP_EOL;
1518 } elseif ($file_type == 'f997') {
1520 // array('Date', 'FileName', 'Ctn_999', 'ta1ctrl', 'RejCt');
1521 for ($i=$rwst; $i<$ln_ct; $i++) {
1522 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1523 $csv_html .= "<tr class=\"$bgc\">".PHP_EOL;
1525 foreach($csv_d[$i] as $idx => $dta) {
1526 if ($idx == 1) {
1527 $fnm = $dta;
1528 $ext = strtolower(substr($dta, -3));
1529 $csv_html .= "<td><a target=\"_blank\" href=\"edi_history_main.php?fvkey=$dta\">$dta</a>&nbsp;&nbsp;<a target=\"_blank\" href=\"edi_history_main.php?fvkey=$dta&readable=yes\">Text</a></td>".PHP_EOL;
1530 } elseif ($idx == 3) {
1531 $csv_html .= "<td><a target=\"_blank\" href=\"edi_history_main.php?btctln=$dta\">$dta</a></td>".PHP_EOL;
1532 } elseif ($idx == 4) {
1533 if ($ext == '999' || $ext == '997') {
1534 $csv_html .= "<td><a class=\"codeval\" target=\"_blank\" href=\"edi_history_main.php?fv997=$fnm&err997=$dta\">$dta</a></td>".PHP_EOL;
1535 } elseif ($ext == 'ta1' || $ext == 'ack') {
1536 $csv_html .= "<td><a class=\"codeval\" target=\"_blank\" href=\"edi_history_main.php?ackfile=$fnm&ackcode=$dta\">$dta</a></td>".PHP_EOL;
1537 } else {
1538 $csv_html .= "<td>$dta</td>".PHP_EOL;
1540 } else {
1541 $csv_html .= "<td>$dta</td>".PHP_EOL;
1544 $csv_html .= "</tr>".PHP_EOL;
1546 } elseif ($file_type == 'ebr' || $file_type == 'ibr' ) {
1547 //['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
1548 for ($i=$rwst; $i<$ln_ct; $i++) {
1549 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1550 $fnm = $csv_d[$i][1];
1551 $btfile = $csv_d[$i][5];
1552 if (intval($csv_d[$i][4]) > 0) {
1553 $rejlink = "<td><a class=\"clmstatus\" target=\"_blank\" href=\"edi_history_main.php?ebrfile=$fnm&ebrclm=any\">{$csv_d[$i][4]}</a></td>";
1554 } else {
1555 $rejlink = "<td>{$csv_d[$i][4]}</td>";
1558 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1559 $csv_html .= "<td><a target=\"_blank\" href=\"edi_history_main.php?fvkey={$csv_d[$i][1]}\">{$csv_d[$i][1]}</a>&nbsp;&nbsp;<a target=\"_blank\" href=\"edi_history_main.php?fvkey={$csv_d[$i][1]}&readable=yes\">Text</a></td>".PHP_EOL;
1560 $csv_html .= "<td>{$csv_d[$i][2]}</td>".PHP_EOL;
1561 $csv_html .= "<td>{$csv_d[$i][3]}</td>".PHP_EOL;
1562 $csv_html .= $rejlink.PHP_EOL;
1563 $csv_html .= "<td><a target=\"_blank\" href=\"edi_history_main.php?fvkey=$btfile\">$btfile</a></td>".PHP_EOL;
1565 $csv_html .= "</tr>".PHP_EOL;
1567 } else {
1569 // the generic case -- for 'file' type tables, the filename is in column 1, as set in the parameters array
1570 // see csv_parameters()
1571 // create header row here
1573 for ($i=$rwst; $i<$ln_ct; $i++) {
1574 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1575 $csv_html .= "<tr class='{$bgc}'>";
1576 foreach($csv_d[$i] as $idx=>$dta) {
1577 if ($idx == $fncol) {
1578 $csv_html .= "<td><a href='edi_history_main.php?fvkey=$dta' target='_blank'>$dta</a></td>".PHP_EOL;
1579 } else {
1580 $csv_html .= "<td>$dta</td>".PHP_EOL;
1583 $csv_html .= "</tr>".PHP_EOL;
1586 } elseif ($csv_type == 'claim') {
1587 // a 'claim' type table $csv_type == 'claim' there is more variation
1588 if ($file_type == 'era') {
1589 // era csv_type is claim col 2 is pid, 3 encounter, 8 is trace
1590 //['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
1591 //['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
1592 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1593 $csv_html .= '<th>Name</th><th>SvcDate</th><th>CLM01</th><th>Status</th><th>Trace</th><th>File</th><th>Payer</th>'.PHP_EOL;
1594 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1595 for ($i=$rwst; $i<$ln_ct; $i++) {
1596 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1597 $nm = $csv_d[$i][0];
1598 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1599 $clm = $csv_d[$i][2];
1600 $sts = $csv_d[$i][3];
1601 $trc = $csv_d[$i][4];
1602 $fnm = $csv_d[$i][5];
1603 $clmid = $csv_d[$i][6];
1604 $msg = $csv_d[$i][6] .' '.$csv_d[$i][7].' '.$csv_d[$i][8];
1605 $pr = $csv_d[$i][9];
1606 $csv_html .= "<tr class='{$bgc}'>";
1607 //Name
1608 $csv_html .= "<td>$nm</td>".PHP_EOL;
1609 //SvcDate
1610 $csv_html .= "<td>$dt</td>".PHP_EOL;
1611 //CLM01
1612 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=&btpid=$clm'>$clm</a></td>".PHP_EOL;
1613 //Status
1614 $csv_html .= "<td>$sts <a class='clmstatus' target='_blank' href='edi_history_main.php?erafn=$fnm&pidenc=$clm&summary=yes'>S</a> &nbsp;&nbsp;<a target='_blank' href='edi_history_main.php?erafn=$fnm&pidenc=$clm&srchtp=encounter'>RA</a></td>".PHP_EOL;
1615 //Trace
1616 $csv_html .= "<td><a target='_blank' href='edi_history_main.php?erafn=$fnm&trace=$trc&srchtp=trace'>$trc</a></td>".PHP_EOL;
1617 //File_835
1618 $csv_html .= "<td title=$fnm><a target='_blank' href='edi_history_main.php?fvkey=$fnm'>x12</a></td>".PHP_EOL;
1619 //ClaimID
1620 $csv_html .= "<td title=$msg>$pr</td>".PHP_EOL;
1622 $csv_html .= "</tr>".PHP_EOL;
1624 } elseif ($file_type == 'dpr') {
1625 // dpr case, only file type is claim,
1626 //['dpr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1627 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1628 $csv_html .= '<th>Name</th><th>SvcDate</th><th>CLM01</th><th>Status</th><th>Batch</th><th>File</th><th>Payer</th>'.PHP_EOL;
1629 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1630 for ($i=$rwst; $i<$ln_ct; $i++) {
1631 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1632 $pidenc = $csv_d[$i][2];
1633 $btfile = $csv_d[$i][4];
1634 $fnm = $csv_d[$i][5];
1635 //$msg = $csv_d[$i][8];
1636 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1637 $csv_html .= "<tr class='{$bgc}'>";
1638 //Name
1639 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1640 //SvcDate
1641 $csv_html .= "<td>$dt</td>".PHP_EOL;
1642 //CLM01
1643 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=$btfile&btpid=$pidenc'>$pidenc</a></td>".PHP_EOL;
1644 //Status
1645 $csv_html .= "<td><a class='clmstatus' target='_blank' href='edi_history_main.php?dprfile=$fnm&dprclm=$pidenc'>{$csv_d[$i][3]}</a></td>".PHP_EOL;
1646 //Batch
1647 $csv_html .= "<td title=$btfile><a target='_blank'href='edi_history_main.php?fvkey=$btfile'>Batch</a></td>".PHP_EOL;
1648 //File
1649 $csv_html .= "<td title=$fnm><a target='_blank'href='edi_history_main.php?fvkey=$fnm'>dpr File</a> <a target='_blank'href='edi_history_main.php?fvkey=$fnm&readable=yes'>Text</a></td>".PHP_EOL;
1650 //Payer
1651 $csv_html .= "<td>{$csv_d[$i][6]}</td>".PHP_EOL;
1653 $csv_html .= "</tr>".PHP_EOL;
1656 } elseif ($file_type == 'ebr' || $file_type == 'ibr') {
1657 //array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1659 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1660 $csv_html .= '<th>Name</th><th>SvcDate</th><th>CLM01</th><th>Status</th><th>Batch</th><th>File</th><th>Payer</th>'.PHP_EOL;
1661 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1662 for ($i=$rwst; $i<$ln_ct; $i++) {
1663 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1665 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1666 $pidenc = $csv_d[$i][2];
1667 $btfile = $csv_d[$i][4];
1668 $fnm = $csv_d[$i][5];
1669 //$msg = $csv_d[$i][8];
1670 $csv_html .= "<tr class='{$bgc}'>";
1671 //Name
1672 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1673 //SvcDate
1674 $csv_html .= "<td>$dt</td>".PHP_EOL;
1675 //CLM01
1676 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=$btfile&btpid=$pidenc'>$pidenc</a></td>".PHP_EOL;
1677 //Status
1678 $csv_html .= "<td><a class='clmstatus' target='_blank' href='edi_history_main.php?ebrfile=$fnm&ebrclm=$pidenc'>{$csv_d[$i][3]}</a></td>".PHP_EOL;
1679 //Batch
1680 $csv_html .= "<td title=$btfile><a target='_blank' href='edi_history_main.php?fvkey=$btfile'>Batch</a></td>".PHP_EOL;
1681 //File
1682 $csv_html .= "<td title=$fnm><a target='_blank'href='edi_history_main.php?fvkey=$fnm'>File</a> <a target='_blank'href='edi_history_main.php?fvkey=$fnm&readable=yes'>R</a></td>".PHP_EOL;
1683 //Payer
1684 $csv_html .= "<td title=$msg>{$csv_d[$i][6]}</td>".PHP_EOL;
1686 $csv_html .= "</tr>".PHP_EOL;
1688 } elseif ($file_type == 'f997') {
1690 //['f997']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'File_997', 'Ctn_837', 'err_seg');
1691 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1692 $csv_html .= '<th>Name</th><th>SvcDate</th><th>CLM01</th><th>Status</th><th>ST</th><th>File</th><th>Batch</th>'.PHP_EOL;
1693 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1694 for ($i=$rwst; $i<$ln_ct; $i++) {
1695 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1697 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1698 $pidenc = $csv_d[$i][2];
1699 $akn = $csv_d[$i][4];
1700 $fnm = $csv_d[$i][5];
1701 $msg = strlen($csv_d[$i][7]) ? $csv_d[$i][7] : 'ST Number';
1703 $csv_html .= "<tr class='{$bgc}'>";
1704 //Name
1705 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1706 //SvcDate
1707 $csv_html .= "<td>$dt</td>".PHP_EOL;
1708 //CLM01
1709 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch={$csv_d[$i][6]}&btpid=$pidenc'>$pidenc</a></td>".PHP_EOL;
1710 //Status
1711 $csv_html .= "<td><a class='clmstatus' target='_blank' href='edi_history_main.php?fv997=$fnm&aknum=$akn'>{$csv_d[$i][3]}</a></td>".PHP_EOL;
1712 //ST
1713 $csv_html .= "<td title='$msg'>$akn</td>".PHP_EOL;
1714 //File
1715 $csv_html .= "<td><a target='_blank' href='edi_history_main.php?fvkey=$fnm'>$fnm</a></td>".PHP_EOL;
1716 //Batch
1717 $csv_html .= "<td><a target='_blank' href='edi_history_main.php?btctln={$csv_d[$i][6]}'>{$csv_d[$i][6]}</a></td>".PHP_EOL;
1719 $csv_html .= "</tr>".PHP_EOL;
1721 } elseif ($file_type == 'f277') {
1723 //['f277']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_277', 'File_277', 'payer_name', 'claim_id', 'bht03_837');
1724 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1725 $csv_html .= '<th>Name</th><th>SvcDate</th><th>CLM01</th><th>Status</th><th>File</th><th>ClaimID</th><th>Batch</th>'.PHP_EOL;
1726 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1727 for ($i=$rwst; $i<$ln_ct; $i++) {
1728 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1729 $csv_html .= "<tr class='{$bgc}'>";
1731 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1732 $btpid = $csv_d[$i][2];
1733 $f277file = $csv_d[$i][5];
1734 $clmid = $csv_d[$i][7];
1735 $bt_bht03 = $csv_d[$i][8];
1736 //$msg277 = (strlen($csv_d[$i][9])) ? $csv_d[$i][9] : '';
1737 //Name
1738 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1739 //SvcDate
1740 $csv_html .= "<td>$dt</td>".PHP_EOL;
1741 //CLM01
1742 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=$bt_bht03&btpid=$btpid'>$btpid</a></td>".PHP_EOL;
1743 //Status
1744 $csv_html .= "<td><a class='clmstatus' target='_blank' href='edi_history_main.php?rspfile=$f277file&pidenc=$btpid&rspstnum={$csv_d[$i][4]}'>{$csv_d[$i][3]}</a></td>".PHP_EOL;
1745 //File
1746 $csv_html .= "<td title='$f277file'><a target='_blank' href='edi_history_main.php?fvkey=$f277file'>File</a></td>".PHP_EOL;
1747 //ClaimID
1748 $csv_html .= "<td>$clmid</td>".PHP_EOL;
1749 //Batch
1750 $csv_html .= "<td title='$bt_bht03'><a target='_blank' href='edi_history_main.php?btctln=$bt_bht03'>Batch</a></td>".PHP_EOL;
1752 $csv_html .= "</tr>".PHP_EOL;
1754 } elseif ($file_type == 'batch') {
1756 //['batch']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' );
1757 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1758 $csv_html .= '<th>Name</th><th>SvcDate</th><th>CLM01</th><th>Ins</th><th>Fee/PtPd</th><th>File</th><th>Provider</th>'.PHP_EOL;
1759 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1760 for ($i=$rwst; $i<$ln_ct; $i++) {
1761 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1763 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1764 $msg = $csv_d[$i][6].' ('.$csv_d[$i][7].')';
1765 $btfile = $csv_d[$i][5];
1767 $csv_html .= "<tr class='{$bgc}'>";
1768 //Name
1769 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1770 //SvcDate
1771 $csv_html .= "<td>$dt</td>".PHP_EOL;
1772 //CLM01
1773 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=$btfile&btpid={$csv_d[$i][2]}'>{$csv_d[$i][2]}</a></td>".PHP_EOL;
1774 //Ins
1775 $csv_html .= "<td>{$csv_d[$i][3]}</td>".PHP_EOL;
1776 //Fee/PtPd
1777 $csv_html .= "<td>$msg</td>".PHP_EOL;
1778 //File
1779 $csv_html .= "<td><a target='_blank' href='edi_history_main.php?fvkey=$btfile'>$btfile</a></td>".PHP_EOL;
1780 //Provider
1781 $csv_html .= "<td>{$csv_d[$i][8]}</td>".PHP_EOL;
1783 $csv_html .= "</tr>".PHP_EOL;
1785 } else {
1787 $csv_html .= '<thead>'.PHP_EOL.'<tr>';
1788 foreach ($csv_d[0] as $h) { $csv_html .= "<th>$h</th>"; }
1789 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1791 for ($i=$rwst; $i<$ln_ct; $i++) {
1792 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1793 $csv_html .= "<tr class='{$bgc}'>";
1794 foreach($csv_d[$i] as $idx=>$dta) {
1795 $csv_html .= "<td>$dta</td>".PHP_EOL;
1797 $csv_html .= "</tr>".PHP_EOL;
1800 } // end body of the table
1801 //$csv_html .= "</tbody>".PHP_EOL."</table>".PHP_EOL."</div>".PHP_EOL;
1802 $csv_html .= "</tbody>".PHP_EOL."</table>".PHP_EOL;
1803 return $csv_html;
1807 * Produce an html rendition of one of our files
1809 * this function accepts a file name array from uploads.php, one file only,
1810 * or a string file path as the filepath argument
1812 * @uses csv_check_filepath()
1813 * @uses csv_parameters()
1814 * @uses csv_x12_segments()
1815 * @param mixed $filepath string or array path to or name of one of our files
1816 * @return string html formatted
1818 function csv_filetohtml ($filepath) {
1820 if (is_array($filepath) && count($filepath) > 0) {
1821 $ftkey = array_keys($filepath);
1822 $type = $ftkey[0];
1823 $ftestpath = $filepath[$type][0];
1824 $bn = basename($ftestpath);
1825 } else {
1826 $bn = basename($filepath);
1827 $params = csv_parameters("ALL");
1829 foreach($params as $ky=>$val) {
1830 if ( !$params[$ky]['regex'] ) { continue; }
1831 if (!preg_match($params[$ky]['regex'], $bn) ) {
1832 continue;
1833 } else {
1834 $type = $params[$ky]['type'];
1835 //$ftestpath = dirname(__FILE__) . $params[$ky]['directory'].DIRECTORY_SEPARATOR.$bn;
1836 $ftestpath = $params[$ky]['directory'].DIRECTORY_SEPARATOR.$bn;
1837 break;
1843 if (!isset($type) ) {
1844 csv_edihist_log("csv_filetohtml: failed to type $bn");
1845 $out_str = "csv_filetohtml: failed to classify $bn <br />". PHP_EOL;
1846 return $out_str;
1849 $fp = csv_check_filepath($ftestpath, $type);
1850 if (!$fp) {
1851 csv_edihist_log("csv_filetohtml: could not get good path for $bn");
1852 $out_str = "csv_filetohtml: could not get good path for $bn <br />". PHP_EOL;
1853 return $out_str;
1855 // different file types
1856 if (strpos("|batch|era|f277|f997|999|837|835|ta1", (string)$type) ) {
1857 // x12 file types
1858 $seg_ar = csv_x12_segments($fp, $type);
1859 if (is_array($seg_ar) && count($seg_ar['segments']) ) {
1860 $txt_ar = $seg_ar['segments'];
1861 $seg_d = $seg_ar['delimiters']['t'];
1862 $fltp = 'x12';
1863 } else {
1864 csv_edihist_log("csv_filetohtml: did not get segments for $bn");
1865 $out_str = "csv_filetohtml: did not get segments for $bn <br />". PHP_EOL;
1866 return $out_str;
1869 } elseif (strpos("|ibr|ebr|dpr|ack", $type) ) {
1870 // clearinghouse file types (newlines)
1871 $fh = fopen($fp, 'r');
1872 if ($fh) {
1873 while (($buffer = fgets($fh, 1024)) !== false) {
1874 $txt_ar[] = $buffer;
1876 if (!feof($fh)) {
1877 csv_edihist_log("csv_filetohtml: failed to open $bn <br />");
1879 fclose($fh);
1880 $fltp = 'ibr';
1881 } else {
1882 csv_edihist_log("csv_filetohtml: did not get lines for $bn");
1883 $out_str = "csv_filetohtml: did not get lines for $bn <br />". PHP_EOL;
1884 return $out_str;
1886 } elseif (strpos("|text", $type) ) {
1887 // clearinghouse readable versions
1888 $txt_ar = file_get_contents($fp);
1889 $fltp = 'text';
1890 if (!$txt_ar) {
1891 csv_edihist_log("csv_filetohtml: did not get contents for $bn");
1892 $out_str = "csv_filetohtml: did not get contents for $bn <br />". PHP_EOL;
1893 return $out_str;
1896 } else {
1897 csv_edihist_log("csv_filetohtml: $type was not matched for $bn");
1898 $out_str = "csv_filetohtml: $type was not matched for $bn <br />". PHP_EOL;
1899 return $out_str;
1901 // we have navigated our checks and read the file
1902 $out_str = "";
1904 // now prepare the html page
1905 $out_str = '';
1906 // use an ordered list format for x12 and ebr/ibr, regular text for text type
1907 if ($fltp == "text") {
1908 $out_str .= "<h4>$bn</h4>
1909 <div class=\"filetext\">
1910 <pre>
1912 $out_str .= $txt_ar;
1913 $out_str .= "
1914 </pre>";
1915 } elseif ($fltp == 'x12') {
1916 $out_str .= "<h4>$bn</h4>
1917 <div class=\"filetext\">
1918 <ol>
1920 foreach($txt_ar as $line) {
1921 $out_str .= "
1922 <li>
1924 $line$seg_d
1925 </p>
1926 </li>
1929 $out_str .= "</ol>
1931 } else {
1932 $out_str .= "<h4>$bn</h4>
1933 <div class=\"filetext\">
1934 <ol>
1936 foreach($txt_ar as $line) {
1937 $out_str .= "
1938 <li>
1940 $line
1941 </p>
1942 </li>
1945 $out_str .= "</ol>
1949 $out_str .= "
1950 </div>
1951 </body>
1952 </html>";
1954 return $out_str;
1958 * Check that the file path we are working with is a readable file.
1960 * If it is a file we have uploaded and we have only the file name
1961 * this function will type the file and find it in the uploaded files directories
1962 * and return the complete path.
1964 * @uses csv_parameters()
1965 * @param string $filename name of a file that is one of our types
1966 * @param string $type optional; one of our file types
1967 * @return string either an empty string or a readable filepath
1969 function csv_check_filepath($filename, $type = "ALL") {
1971 // if file is readable, just return it
1972 if ( is_file($filename) && is_readable($filename) ) {
1973 return $filename;
1976 $goodpath = "";
1977 $fn = basename($filename);
1979 if ($type != "ALL") {
1980 $p = csv_parameters($type);
1981 if (is_array($p) && array_key_exists('type', $p) ) {
1982 // type was found
1983 $fp = $p['directory'].DIRECTORY_SEPARATOR.$fn;
1984 if ( is_file($fp) && is_readable($fp) ) {
1985 $goodpath = $fp;
1987 } else {
1988 csv_edihist_log("csv_check_filepath: invalid type $type");
1990 } else {
1991 $p_ar = csv_parameters("ALL");
1992 foreach ($p_ar as $tp=>$par) {
1994 if ( !$p_ar[$tp]['regex'] || !preg_match($p_ar[$tp]['regex'], $fn) ) {
1995 continue;
1996 } else {
1998 $fp = $p_ar[$tp]['directory'].DIRECTORY_SEPARATOR.$fn;
1999 if ( is_file($fp) && is_readable($fp) ) {
2000 $goodpath = $fp;
2002 break;
2007 return $goodpath;
2011 * Verify readibility and check for some expected content.
2013 * @uses csv_check_filepath()
2014 * @param string $file_path full path to file
2015 * @param string $type one of our file types
2016 * @param bool $val_array optional; default is false, whether to return filepath only
2017 * or array(filepath, next_segment)
2018 * @return string|array string file path or array if valid, otherwise 'false'
2020 function csv_verify_file( $file_path, $type, $val_array=FALSE ) {
2021 // check whether $file_path actually leads to a plausible x12 file
2022 // and supply proper directory if needed
2023 $fp = csv_check_filepath($file_path, $type);
2025 // verify that the file is correct format by checking the first ST segment
2026 if ($fp) {
2028 $type = strtolower($type);
2030 if ($type == "batch" || $type == "837") { $st_str = "ST*837"; $next_segment = "GS"; $slen = 250; }
2031 if ($type == "era" || $type == "835") { $st_str = "ST*835"; $next_segment = "GS"; $slen = 250; }
2032 if ($type == "997" || $type == "f997") { $st_str = "ST*997"; $next_segment = "TA1"; $slen = 250; }
2033 if ($type == "999" || $type == "f999") { $st_str = "ST*999"; $next_segment = "TA1"; $slen = 250; }
2034 if ($type == "277" || $type == "f277") { $st_str = "ST*277"; $next_segment = "GS"; $slen = 250; }
2035 if ($type == "271" || $type == "f271") { $st_str = "ST*271"; $next_segment = "GS"; $slen = 250; }
2036 if ($type == "ta1") { $st_str = "TA1*"; $next_segment = "TA1"; $slen = 200; }
2037 // note the ebr, ibr, dpr ack checks will not be valid for years < 2010 or > 2019
2038 if ($type == "ebr") { $st_str = "1|201"; $slen = 10; }
2039 if ($type == "ibr") { $st_str = "1|201"; $slen = 10; }
2040 if ($type == "dpr") { $st_str = "DPR|201"; $slen = 10; }
2041 if ($type == "ack") { $st_str = "1|201"; $slen = 10; }
2042 if ($type == "text") { $st_str = "Date Received"; $slen = 800; }
2043 // for proprietary file formats, if we have a list of clearinghouses,
2044 // the $st_str could be "Availity|Emdeon|ABC " etc. and name probably found in the first 100 characters
2045 // instead of Date Received, which I just guess will be standard and within the first 500 characters
2046 $f_str = file_get_contents($fp, FALSE, NULL, 0, $slen);
2047 $st_pos = strpos($f_str, $st_str);
2049 // special check for 997/999 types, since 999 may be type 997
2050 if (($type == "997" || $type == "f997") && preg_match('/\.999$/', $fp) ) {
2051 $st_pos = strpos($f_str, "ST*999");
2052 $next_segment = "TA1";
2054 //$f_str = file_get_contents($fp);
2055 //$st_pos = strpos(substr($f_str, 0, $slen), $st_str);
2056 if ( $st_pos === FALSE ) {
2057 // did not find the magic word
2058 csv_edihist_log ("csv_verify_file: Error, not a valid $type file: $file_path");
2059 //echo "ibr_era_check_path Error, not a valid $type file: $file_path <br />"
2060 $fp = FALSE;
2061 $next_segment = FALSE;
2063 } else {
2064 $next_segment = '';
2066 if ($val_array) {
2067 return array($fp, $next_segment);
2068 } else {
2069 return $fp;
2074 * Parse Availity ebr, ibr, or dpr file to an array
2076 * This function will return a multidimensional array that is indexed per line of the file
2077 * and another array of each field or element in the line. It uses the
2078 * constant IBR_DELIMITER as the element delimiter
2080 * @param string $file_path complete path to file
2081 * @return array multidimensional array
2083 function csv_ebr_filetoarray ($file_path ) {
2084 // read the ibr, ebr, dpr file into an array of arrays
2085 // since the file is multi-line, use fgets()
2086 //$delim = "|";
2087 $ext = substr($file_path, -3);
2088 $fp = csv_verify_file( $file_path, $ext);
2089 if (!$fp) {
2090 csv_edihist_log("csv_ebr_filetoarray: failed to verify $file_path");
2091 return false;
2093 $ar_ibr = array();
2094 $fh = fopen($fp, 'r');
2095 if ($fh) {
2096 while (($buffer = fgets($fh, 4096)) !== false) {
2097 $ar_ibr[] = explode ( IBR_DELIMITER, trim($buffer) );
2099 } else {
2101 csv_edihist_log( "csv_ebr_filetoarray Error: failed to read " . $file_path );
2103 fclose($fh);
2104 return $ar_ibr;
2109 * Parse an x12 segment text into an array.
2111 * @param string $sgmt_str -- a substring of all or part of a segment
2112 * @param string $seg_term -- segment delimiter default is "~"
2113 * @param string $elem_delim -- element delimiter default is "*"
2114 * @return array -- exploded $sgmt_str by $element_d
2116 function csv_x12_segment_to_array($sgmt_str, $seg_term = "~", $elem_delim = "*") {
2118 // allow for imprecise selecting of segment strings
2120 $slen = strlen($sgmt_str);
2121 if (!$slen) { return FALSE; }
2122 if (!strpos($sgmt_str, $elem_delim)) { return FALSE; }
2123 // find segment delimiters (up to two)
2124 $p1 = strpos($sgmt_str, $seg_term);
2125 $p2 = ($p1) ? strpos($sgmt_str, $seg_term, $p1+1 ) : FALSE;
2126 if ($p1===FALSE) {
2127 // assume we have just the segment text
2128 $seg1 = trim($sgmt_str);
2129 } elseif ($p1 && $p2 && ($p2 > $p1) && ($p2-$p1 < $slen) ) {
2130 // assume we have end of segment ~ segment ~ begining of next segment
2131 $seg1 = substr($sgmt_str, $p1+1, $p2-$p1-1);
2132 } elseif ($p1 !== FALSE && $p1 == 0) {
2133 // assume we have ~segment
2134 $seg1 = substr($sgmt_str, $p1+1);
2135 } elseif ($p1 !== FALSE && $p1+1 == $slen ) {
2136 // assume we have segment~
2137 $seg1 = substr($sgmt_str, 0, $p1-1);
2138 } elseif ($p1 !== FALSE && $p1+1 < $slen ) {
2139 // assume we have segment~ start of segment
2140 $seg1 = substr($sgmt_str, 0, $p1-1);
2141 } else {
2142 // no conjecture matched, just use it
2143 $seg1 = trim($sgmt_str);
2146 $ar_seg = explode ($elem_delim, $seg1 );
2148 return $ar_seg;
2153 * Extract x12 delimiters from the ISA segment
2155 * There are obviously easier/faster ways of doing this, but I wanted to be able
2156 * to possibly extract these needed values from malformed or mishandled
2157 * files, so we go character by character. The array returned is empty on error, otherwise:
2158 * <pre>
2159 * array('t'=>segment terminator, 'e'=>element delimiter,
2160 * 's'=>sub-element delimiter, 'r'=>repetition delimiter)
2161 * </pre>
2163 * @param string $isa_str126 first 126 characters of x12 file
2164 * @param string $next_segid the segment ID immediately following ISA segment
2165 * @return mixed array or false on error
2167 function csv_x12_delimiters($isa_str126, $next_segid) {
2168 // this function reads the ISA segment and into the next segment of
2169 // an x12 file, to determine the delimiters,
2170 // ISA segment is 106 characters, 16 elements, so the $next_segid could be dispensed with
2172 if (substr($isa_str126, 0, 3) != "ISA") {
2173 // not the starting 126 characters
2174 csv_edihist_log("csv_x12_delimiters Error: isa_str126 does not begin with ISA");
2175 return FALSE;
2177 if (! strpos($isa_str126, $next_segid) ) {
2178 // not the starting 126 characters
2179 csv_edihist_log("csv_x12_delimiters Error: next_segment $next_segid not in isa_str126 ");
2180 return FALSE;
2182 // $ns_pos = strpos($isa_str126, $next_segid);
2183 // $elem_pos = strrpos (substr($isa_str126, 0, $ns_pos-1), $elem_delim);
2184 // $dstr = substr($isa_str126, $elem_pos, $ns_pos-1);
2186 $ret_ar = array();
2187 $delim_ct = 0;
2188 $seg2_len = strlen($next_segid); // usually "GS" but could be "TA1" or whatever the specification says
2189 $rep_d = ""; // repetition delimiter ISA11 5010A1 - per Availity EDI guide
2191 $elem_delim = substr($isa_str126, 3, 1); // ISA*
2192 $s = '';
2193 $chars = strlen($isa_str126);
2195 for ($i = 0; $i < $chars; $i++) {
2196 if (strlen($s) >= 3 && substr($s, -$seg2_len) == $next_segid) { break; }
2197 $c = substr($isa_str126, $i, 1);
2198 if ($c == $elem_delim) {
2199 if ($delim_ct == 11) { $rep_d = substr($s, 1, 1); }
2200 $s = $elem_delim;
2201 $delim_ct++;
2202 } else {
2203 $s .= $c;
2205 // there are 16 elements in ISA segment
2206 if ($delim_ct > 16) {
2207 // incorrect $next_segid argument
2208 csv_edihist_log("csv_x12_delimiters: incorrect next_segment $next_segid does not follow ISA");
2209 return FALSE;
2212 if (strlen($s) >= 5) {
2213 $ret_ar["t"] = $s[2];
2214 $ret_ar["e"] = $s[0];
2215 $ret_ar["s"] = $s[1];
2216 $ret_ar["r"] = $rep_d;
2217 } else {
2218 csv_edihist_log("csv_x12_delimiters: Invalid delimiters $s");
2220 return $ret_ar;
2224 * from php help: tleffler [AT] gmail [DOT] com 12-May-2011 08:55
2226 * Not used, but possibly interesting. Thought is to pass a file handle as an argument
2227 * @param mixed $possibleResource
2228 * @return bool
2230 function isResource ($possibleResource) { return !is_null(@get_resource_type($possibleResource)); }
2233 * Parse x12 file into array of segments.
2235 * This function relies on csv_x12_delimiters() to get the delimiters array
2236 * and on csv_verify_file() to supply the next_segment value.
2237 * There are some basic verifications and failures are noted in the log.
2238 * <pre>
2239 * 'path'=>pathtofile
2240 * 'delimiters'=>array from x12_delimiters()
2241 * 'segments'=>segments[i]=>segment text
2242 * if $seg_array is true then segments[i]=>array of elements
2243 * </pre>
2245 * @uses csv_x12_delimiters()
2246 * @uses csv_verify_file()
2247 * @param string $file_path the full path for the file
2248 * @param string $type one of batch|837|era|835|997|999|277|271
2249 * @param bool $seg_array whether each segment should be made into an array of elements
2250 * @return array|bool array['delimiters']['segments']['path'], or false on error
2252 function csv_x12_segments($file_path, $type, $seg_array = FALSE) {
2253 // do verifications
2254 $fp_ar = csv_verify_file($file_path, $type, TRUE );
2256 if (!$fp_ar || !$fp_ar[0]) {
2257 csv_edihist_log ("csv_x12_segments: verification failed for $file_path");
2258 return FALSE;
2261 if ($fp_ar) {
2263 $fp =$fp_ar[0];
2264 $next_segment = $fp_ar[1];
2265 $f_str = file_get_contents($fp);
2267 // verify $delimiters
2268 $delimiters = csv_x12_delimiters(substr($f_str,0,126), $next_segment);
2269 $dlm_ok = FALSE;
2270 // here, expect $delimiters['t'] ['e'] ['s'] ['r']
2271 if (is_array($delimiters) && array_keys($delimiters) == array('t', 'e', 's', 'r') ) {
2272 $dlm_ok = TRUE;
2273 $seg_d = $delimiters['t'];
2274 $elem_d = $delimiters['e'];
2275 $subelem_d = $delimiters['s'];
2276 $rep_d = $delimiters['r'];
2277 } else {
2278 csv_edihist_log ("csv_x12_segments: invalid delimiters or delimiters wrong for $fp");
2279 return FALSE;
2282 // OK, now initialize variables
2283 $fn = basename($fp);
2284 $ar_seg = array();
2285 $ar_seg['path'] = $fp;
2286 $ar_seg['delimiters'] = $delimiters;
2287 $ar_seg['segments'] = array();
2288 $seg_pos = 0; // position where segment begins
2289 $st_segs_ct = 0; // segments in ST-SE envelope
2290 $se_seg_ct = ""; // segment count from SE segment
2291 $isa_segs_ct = 0; // segments in ISA envelope
2292 $isa_ct = 0; // ISA envelope count
2293 $iea_ct = 0; // IEA count
2294 $trnset_seg_ct = 0; // segments by sum of isa segment count
2296 $isa_str = "ISA".$elem_d; // to reduce evaluations
2297 $iea_str = "IEA".$elem_d;
2298 $st_str = "ST".$elem_d;
2299 $se_str = "SE".$elem_d;
2301 $idx = -1;
2302 $moresegs = true;
2303 while ($moresegs) {
2304 $idx++;
2305 // extract each segment from the file text
2306 $seg_end = strpos($f_str, $seg_d, $seg_pos);
2307 $moresegs = strpos($f_str, $seg_d, $seg_end+1);
2309 $seg_text = substr($f_str, $seg_pos, $seg_end-$seg_pos);
2310 $seg_pos = $seg_end + 1;
2312 // we trim in case there are line or carriage returns
2313 $seg_text = trim($seg_text);
2315 // check for non ASCII basic characters. Note reg_ex '/[^\x20-\xFF]/' allows extended ASCII characters
2316 // this is partly file syntax and partly protection -- we don't want to process an imposter
2317 // We are mostly concerned with \x00 to \x19, the "control" characters, but apparently some are allowed
2319 if (preg_match_all('/[^\x20-\x7E]/', $seg_text, $matches)) {
2320 csv_edihist_log ("csv_x12_segments: Non-basic ASCII character in segment ($idx) in file $fn");
2321 // quit here? return false; -- actually files have probably been scanned before in upload.php
2322 // also x12 files have more allowed characters than these
2324 if ($seg_array) {
2325 $ar_seg['segments'][] = explode($elem_d, $seg_text);
2326 } else {
2327 $ar_seg['segments'][] = $seg_text;
2329 $st_segs_ct++;
2330 $isa_segs_ct++;
2332 // some checks, if wanted
2333 if (substr($seg_text, 0, 4) == $isa_str) {
2334 $isa_seg = explode($elem_d, $seg_text);
2335 $isa_id = $isa_seg[13];
2336 $isa_segs_ct = 1;
2337 $isa_ct++;
2338 continue;
2341 if (substr($seg_text, 0, 3) == $st_str) {
2342 // $e = strpos($seg_text, $elem_d, 8); // ST*835* is 7 characters
2343 // $st02 = substr($seg_text, 7, $e-7);
2344 $st_ar = explode($elem_d, $seg_text);
2345 $st_num = $st_ar[2];
2346 $st_segs_ct = 1;
2347 continue;
2349 if (substr($seg_text, 0, 3) == $se_str) {
2350 $se_ar = explode($elem_d, $seg_text);
2351 $se_seg_ct = $se_ar[1];
2352 $se_num = $se_ar[2];
2353 if ($se_num != $st_num) {
2354 csv_edihist_log ("csv_x12_segments: ST-SE number mismatch $st_num $se_num in $fn");
2356 if (intval($se_seg_ct) != $st_segs_ct) {
2357 csv_edihist_log ("csv_x12_segments: ST-SE segment count mismatch $st_segs_ct $se_seg_ct in $fn");
2359 continue;
2361 if (substr($seg_text, 0, 4) == $iea_str) {
2362 $iea_seg = explode($elem_d, $seg_text);
2363 $iea_id = $iea_seg[2];
2364 $iea_ct++;
2366 if ($isa_id != $iea_id) {
2367 csv_edihist_log ("csv_x12_segments: ISA-IEA identifier mismatch set $iea_ct in $fn");
2369 if ($iea_ct == $isa_ct) {
2370 $trnset_seg_ct += $isa_segs_ct;
2371 if ($idx+1 != $trnset_seg_ct ) { //
2372 csv_edihist_log ("csv_x12_segments: IEA segment count error ({idx+1}:$trnset_seg_ct set) $iea_ct in $fn");
2374 } else {
2375 csv_edihist_log ("csv_x12_segments: ISA-IEA count mismatch set $isa_ct $iea_ct in $fn");
2381 return $ar_seg;