minor bug fix
[openemr.git] / library / edihistory / csv_record_include.php
blob9d17b88267b662c59f54113e1f8e76ac1c6e9f5b
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, ta1, 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 = $params['directory'].DIRECTORY_SEPARATOR;
539 $typedir = basename($params['directory']);
540 $ext_re = $params['regex'];
541 $dirfiles = array();
543 if (is_dir($search_dir)) {
544 if ($dh = opendir($search_dir)) {
545 while (($file = readdir($dh)) !== false) {
546 if (is_file($search_dir.$file) ) {
547 if (preg_match($ext_re, $file) ) {
548 $dirfiles[] = $file;
549 } elseif ($typedir == 'f997') {
550 $ext = substr($file, -3);
551 // no error, since ack|ta1 files are put there
552 if ($type == 'f997') {
553 if ($ext == 'ack' || $ext == 'ta1') { continue; }
554 } elseif ($type == 'ack') {
555 if (ext == '999' || ext == '997' || $ext == 'ta1') { continue; }
556 } elseif ($type == 'ta1') {
557 if (ext == '999' || ext == '997' || $ext == 'ack') { continue; }
559 } else {
560 //if ($file == '.' || $file == '..') { continue; } // . and .. are not files
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;
613 // consider array_shift($processed_files) to drop the header row (too slow)
614 // consider array_unique($processed_files) becasue files may be listed several times
615 return $processed_files;
616 } // end function
620 * Give an array of files in the storage directories that are not in the csv record
622 * @param string $type -- one of our types
623 * @return array
625 function csv_newfile_list($type) {
627 //f277 f997 ack batch csv ebr era ibr text
628 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ack|ta1", $type) ) {
629 if ($type != 'text') {
630 csv_edihist_log("csv_newfile_list: incorrect type $type");
632 return FALSE;
635 $dir_files = csv_dirfile_list ($type);
636 $csv_files = csv_processed_files_list ($type);
637 // $dir_files should come first in array_diff()
638 if (empty($dir_files)) {
639 // logic error -- fixed; if dir_files is empty, there are no files of that type
640 //$ar_new = $csv_files;
641 $ar_new = $dir_files;
642 } else {
643 $ar_new = array_diff($dir_files, $csv_files);
646 return $ar_new;
651 * Give the column headings for the csv files
653 * @param string $file_type -- one of our types batch|era|ibr|ebr|dpr|f277|f997, etc.
654 * @param string $csv_type -- one of 'file' or 'claim'
655 * @return array
657 function csv_files_header($file_type, $csv_type) {
659 if (! strpos("|era|835|f997|999|ibr|ebr|dpr|f277|batch|837|ta1|ack", $file_type) ) {
660 csv_edihist_log("csv_files_header error: incorrect file type $file_type");
661 return FALSE;
663 if (!strpos('|file|claim', $csv_type) ) {
664 csv_edihist_log("csv_files_header error: incorrect csv type $csv_type");
665 return FALSE;
668 $ft = strpos('|277', $file_type) ? 'f277' : $file_type;
669 $ft = strpos('|835', $file_type) ? 'era' : $ft;
670 $ft = strpos('|837', $file_type) ? 'batch' : $ft;
671 $ft = strpos('|999|997|ack|ta1', $file_type) ? 'f997' : $ft;
673 $csv_hd_ar = array();
674 // actually, 'ack' and 'ta1' are probably redundant, since they are interpreted as 'f997'
675 $csv_hd_ar['ack']['file'] = array('Date', 'FileName', 'isa13', 'ta1ctrl', 'Code');
676 $csv_hd_ar['ebr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
677 $csv_hd_ar['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
679 $csv_hd_ar['batch']['file'] = array('Date', 'FileName', 'Ctn_837', 'claim_ct', 'x12_partner');
680 $csv_hd_ar['ta1']['file'] = array('Date', 'FileName', 'Ctn_ta1', 'ta1ctrl', 'Code');
681 $csv_hd_ar['f997']['file'] = array('Date', 'FileName', 'Ctn_999', 'ta1ctrl', 'RejCt');
682 $csv_hd_ar['f277']['file'] = array('Date', 'FileName', 'Ctn_277', 'Accept', 'AccAmt', 'Reject', 'RejAmt');
683 $csv_hd_ar['era']['file'] = array('Date', 'FileName', 'Trace', 'claim_ct', 'Denied', 'Payer');
685 $csv_hd_ar['ebr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
686 $csv_hd_ar['ibr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
687 $csv_hd_ar['dpr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
689 $csv_hd_ar['batch']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' );
690 $csv_hd_ar['f997']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'File_997', 'Ctn_837', 'err_seg');
691 $csv_hd_ar['f277']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_277', 'File_277', 'payer_name', 'claim_id', 'bht03_837');
692 $csv_hd_ar['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
694 return $csv_hd_ar[$ft][$csv_type];
699 * Determine whether an array is multidimensional
701 * @param array
702 * @return bool false if arrayis multidimensional
704 function csv_singlerecord_test ( $array ) {
705 // the two versions of count() are compared
706 // if the array has a sub-array, count recursive is greater
707 $is_sngl = count($array, COUNT_RECURSIVE ) == count( $array, COUNT_NORMAL);
709 return $is_sngl;
713 * A multidimensional array will be flattened to a single row.
715 * @param array $array array to be flattened
716 * @return array
718 function csv_array_flatten($array) {
720 if (!is_array($array)) {return FALSE;}
721 $result = array();
722 foreach ($array as $key => $value) {
723 if (is_array($value)) {
724 $result = array_merge($result, csv_array_flatten($value));
725 } else {
726 $result[$key] = $value;
729 return $result;
733 * Append rows to one of the csv record files.
735 * @uses csv_singlerecord_test()
736 * @uses csv_parameters()
737 * @uses csv_files_header()
738 * @param array $csv_data the data array, either file data or claim data
739 * @param string $file_type which of our file types to use
740 * @param string $csv_type either 'claim' or 'file'
741 * @return int number of characters written per fputcsv()
743 function csv_write_record($csv_data, $file_type, $csv_type) {
745 if (!is_array($csv_data)) { return FALSE;}
746 // use CSV_RECORD class to write ibr or ebr claims data to the csv file
747 // csv, batch, ibr, ebr, f997, or era
748 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ta1|ack", $file_type) ) {
749 csv_edihist_log("csv_write_record error: incorrect file type $file_type");
750 return FALSE;
753 $ft = $file_type;
754 $ft = strpos("|835", $file_type) ? 'era' : $ft;
755 $ft = strpos("|837", $file_type) ? 'batch' : $ft;
756 $ft = strpos("|999|ack|ta1", $file_type) ? 'f997' : $ft;
758 $params = csv_parameters($ft);
760 if ($csv_type == "claim") {
761 $fp = $params['claims_csv'];
762 } elseif ($csv_type == "file") {
763 $fp = $params['files_csv'];
764 } else {
765 csv_edihist_log("csv_writedata_csv error: incorrect csv type $csv_type");
766 return FALSE;
769 $fh = fopen( $fp, 'a');
770 // count characters written -- returned by fputcsv
771 $indc = 0;
772 // if we fail to open the file, return the result, expect FALSE
773 if (!$fh) { return FALSE; }
774 // test for a new file
775 if ( filesize($fp) === 0 ) {
776 $ar_h = csv_files_header($file_type, $csv_type);
777 $td = fgetcsv($fh);
778 if ($td === FALSE || $td === NULL ) {
779 // assume we have an empty file
780 // write header row if this is a new csv file
781 if (count($ar_h) ) {
782 $indc += fputcsv ( $fh, $ar_h );
787 // test array for dimension counts
788 $is_sngl = csv_singlerecord_test($csv_data) ;
789 if ( $is_sngl ) {
790 $indc += fputcsv ( $fh, $csv_data );
791 } else {
792 // multi-dimensional array -- we rely on array_flatten to
793 // assure us that the array depth is 1
794 foreach ($csv_data as $row) {
795 $wr = csv_array_flatten($row);
796 // $wr is false if $row is not an array
797 if ($wr) {
798 $indc += fputcsv ( $fh , $wr );
799 } else {
800 continue;
804 fclose($fh);
806 return $indc;
810 * Search a csv record file and return the row or values from selected columns
812 * This function requires that the $search_ar parameter be an array
813 * with keys ['s_val']['s_col']['r_cols'], and 'r_cols' is an array
814 * 's_val' is the search value, s_col is the column to check, r_cols is an array
815 * of column numbers from which values are returned. If r_cols is not an array,
816 * then the entire row will be returned. If the 'expect' parameter is 1, then
817 * the search will stop after the first success and return the result. Otherwise, the
818 * entire file will be searched.
819 * ex: csv_search_record('batch', 'claim', array('s_val'=>'20120115', 's_col'=>1, 'r_cols'=>array(0, 1, 2, 8)), "2" )
821 * @uses csv_parameters()
822 * @param string $file_type
823 * @param string $csv_type
824 * @param array $search_ar
825 * @param mixed $expect
826 * @return array
828 function csv_search_record($file_type, $csv_type, $search_ar, $expect="1") {
830 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ack", $file_type) ) {
831 csv_edihist_log("csv_search_record: incorrect file type $file_type");
832 return FALSE;
835 $params = csv_parameters($file_type);
837 if ($csv_type == "claim") {
838 $fp = $params['claims_csv'];
839 } elseif ($csv_type == "file") {
840 $fp = $params['files_csv'];
841 } else {
842 csv_edihist_log("csv_search_record: incorrect csv type $csv_type");
843 return FALSE;
846 if (!is_array($search_ar) || array_keys($search_ar) != array('s_val', 's_col', 'r_cols')) {
847 csv_edihist_log("csv_search_record: invalid search criteria");
848 return FALSE;
850 $sv = $search_ar['s_val'];
851 $sc = $search_ar['s_col'];
852 $rv = (is_array($search_ar['r_cols']) && count($search_ar['r_cols'])) ? $search_ar['r_cols'] : 'all';
853 $ret_ar = array();
854 $idx = 0;
856 if (($fh1 = fopen($fp, "r")) !== FALSE) {
857 while (($data = fgetcsv($fh1)) !== FALSE) {
858 // check for a match
859 if ($data[$sc] == $sv) {
860 if ($rv == 'all') {
861 $ret_ar[$idx] = $data;
862 } else {
863 // now loop through the 'r_cols' array for data index
864 $dct = count($data);
865 foreach($rv as $c) {
866 // make sure we don't access a non-existing index
867 if ($c >= $dct) { continue; }
869 $ret_ar[$idx][] = $data[$c];
872 $idx++;
873 if ($expect == '1') { break; }
876 fclose($fh1);
877 } else {
878 csv_edihist_log("csv_search_record: failed to open $fp");
879 return false;
881 if (empty($ret_ar) ) {
882 return false;
883 } else {
884 return $ret_ar;
889 * Search the 'claims' csv table for the patient control and find the associated file name
891 * In 'claims' csv tables, clm01 is position 2 number is pos 4, and filename is pos 5;
892 * except in ebr, ibr, and dpr files which have batch name in pos 4. See the
893 * csv files column headings for more information.
895 * @uses csv_parameters()
896 * @uses csv_pid_enctr_parse()
897 * @see csv_files_header()
898 * @param string patient control-- pid-encounter, pid, or encounter
899 * @param string filetype batch, era, f277, f997, ibr, ebr, dpr
900 * @param string search type encounter, pid, or ptctln
901 * @return array|bool [i](pid_encounter, number, filename) or false on error
903 function csv_file_with_pid_enctr ($ptctln, $filetype='batch', $srchtype='encounter' ) {
905 // return array of [i](pid_encounter, filename), there may be more than one file
907 if (!$ptctln) {
908 csv_edihist_log("csv_file_with_pid_enctr: missing encounter data");
909 //return "invalid encounter data<br />" . PHP_EOL;
910 return false;
912 // IBR_FTYPES
913 if (! strpos('|era|835|f997|999|ibr|ebr|dpr|f277|batch|837|ta1|ack', $filetype) ) {
914 csv_edihist_log("csv_file_with_pid_enctr: incorrect file type $filetype");
915 return false;
916 } else {
917 $params = csv_parameters($filetype);
918 //$fp = isset($params['claims_csv']) ? dirname(__FILE__).$params['claims_csv'] : false;
919 $fp = isset($params['claims_csv']) ? $params['claims_csv'] : false;
920 if (!$fp) {
921 csv_edihist_log("csv_file_with_pid_enctr: incorrect file type $filetype");
922 return false;
926 $enctr = trim($ptctln);
928 preg_match('/\D/', $enctr, $match2, PREG_OFFSET_CAPTURE);
930 if (count($match2)) {
932 if ($srchtype != 'ptctln') {
933 $idar = csv_pid_enctr_parse($enctr);
934 if (is_array($idar) && count($idar)) {
935 $p = strval($idar['pid']);
936 $plen = strlen($p);
937 $e = strval($idar['enctr']);
938 $elen = strlen($e);
939 } else {
940 csv_edihist_log("csv_file_with_pid_enctr: error parsing pid_encounter $pid_enctr");
941 return false;
943 } else {
944 $pe = $enctr;
946 } else {
947 // no match from preg_match, so $enctr has no non-digit characer like '-'
948 if ($srchtype == 'ptctln') {
949 if (strlen($enctr) > IBR_ENCOUNTER_DIGIT_LENGTH) {
950 $pe = substr($enctr, 0, strlen($enctr)-IBR_ENCOUNTER_DIGIT_LENGTH) .'-'.substr($enctr, -IBR_ENCOUNTER_DIGIT_LENGTH);
951 } else {
952 // no pid, so change search type to encounter only
953 $srchtype = 'encounter';
956 $p = strval($enctr);
957 $e = strval($enctr);
958 $plen = strlen($p);
959 $elen = strlen($e);
962 $ret_ar = array();
963 // in 'claims' csv tables, clm01 is position 2 and filename is position 5
964 if (($fh1 = fopen($fp, "r")) !== FALSE) {
965 if ($srchtype == 'encounter') {
966 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
967 // check for a match
968 if (substr($data[2], -$elen) == $e) {
969 // since e=123 will match 1123 and 123
970 $peval = csv_pid_enctr_parse($data[2]);
971 if (is_array($peval) && count($peval)) {
972 if ($peval['enctr'] == $e) {
973 $ret_ar[] = array($data[2], $data[4], $data[5]);
978 } elseif ($srchtype == 'pid') {
979 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
980 // check for a match
981 if (substr($data[2], 0, $plen) == $p) {
982 // since p=123 will match 1123 and 123
983 $peval = csv_pid_enctr_parse($data[2]);
984 if (is_array($peval) && count($peval)) {
985 if ($peval['pid'] == $p) {
986 $ret_ar[] = array($data[2], $data[4], $data[5]);
991 } else {
992 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
993 // check for a match
994 if ($data[2] == $pe) {
995 $ret_ar[] = array($data[2], $data[4], $data[5]);
999 fclose($fh1);
1000 } else {
1001 csv_edihist_log("csv_file_with_pid_enctr: failed to open csv file ");
1002 return false;
1004 return $ret_ar;
1008 * get the x12 file containing the control_num ISA13
1010 * The csv for x12 files 999, 277, 835, 837 has the control number in pos 2
1011 * and the filename in pos 1. This is a convenience function, since the actual
1012 * work is done by csv_search_record()
1014 * @uses csv_search_record()
1015 * @param string $control_num the interchange control number, isa13
1016 * @return string the file name
1018 function csv_file_by_controlnum($type, $control_num) {
1019 // get the batch file containing the control_num
1021 if (! strpos("|era|f997|f277|batch|ta1", $type) ) {
1022 csv_edihist_log("csv_file_by_controlnum: incorrect file type $type");
1023 return FALSE;
1025 // $search_ar should have keys ['s_val']['s_col'] array(['r_cols'][])
1026 // like "batch', 'claim, array(9, '0024', array(1, 2, 7))
1027 $fn = '';
1028 if ($type == 'era') {
1029 $ctln = trim(strval($control_num));
1030 } else {
1031 $ctln = (strlen($control_num) >= 9) ? substr($control_num, 0, 9) : trim(strval($control_num));
1033 $search = array('s_val'=>$ctln, 's_col'=>2, 'r_cols'=>array(1));
1034 $result = csv_search_record($type, 'file', $search, "1");
1035 if (is_array($result) && count($result[0]) == 1) {
1036 $fn = $result[0][0];
1038 return $fn;
1042 * A function to try and assure the pid-encounter is correctly parsed
1044 * assume a format of pid-encounter, since that is sent in the OpenEMR x12 837
1045 * however, in case payer mangles the pid-encounter by dropping the separator,
1046 * check value and use IBR_ENCOUNTER_DIGIT_LENGTH constant
1048 * @param string $pid_enctr the value from element CPL01
1049 * return array array('pid' => $pid, 'enctr' => $enc)
1051 function csv_pid_enctr_parse( $pid_enctr ) {
1052 // evaluate the patient account field
1054 if (!$pid_enctr || !is_string($pid_enctr) ) {
1055 csv_edihist_log("csv_pid_enctr_parse: invalid argument");
1056 return false;
1058 $pval = trim($pid_enctr);
1059 preg_match('/\D/', $pval, $match2, PREG_OFFSET_CAPTURE);
1060 $inv_split = (count($match2)) ? preg_split('/\D/', $pval, 2, PREG_SPLIT_NO_EMPTY) : false;
1061 if ($inv_split) {
1062 $pid = $inv_split[0];
1063 $enc = $inv_split[1];
1064 } elseif ( preg_match('/20[01]{1}[0-9]{1}(0[0-9]{1}|1[0-2]{1})[0-3]{1}[0-9]{1}/', $pval) ) {
1065 // encounter numbers can also be Ymd like 20110412
1066 $enc = $pval;
1067 $pid = '';
1068 } else {
1069 $enc = (strlen($pval) >= IBR_ENCOUNTER_DIGIT_LENGTH) ? substr($pval, -IBR_ENCOUNTER_DIGIT_LENGTH) : $pval;
1070 $pid = (strlen($pval) > IBR_ENCOUNTER_DIGIT_LENGTH) ? substr($pval, 0, (strlen($pval)-IBR_ENCOUNTER_DIGIT_LENGTH)) : '';
1072 return array('pid' => $pid, 'enctr' => $enc);
1077 * This function is supposed to allow the downloading of a file.
1079 * Not used or tested -- do not use. Since the users cannot scan the directories,
1080 * the file to be downloaded would have to be selected from a csv table display
1081 * or some other listing produced by reading the directories.
1083 * @todo implement this function
1084 * @param string $filename
1085 * @return void --save file dialogue
1087 function csv_download_file( $filename ){
1088 // adapted from http://php.net/manual/en/function.header.php
1089 // phpnet at holodyn dot com 31-Jan-2011 01:01
1090 // Must be fresh start
1091 // ///////////////////// this function not used as of now and probably doesn't work
1092 //////////////////////////////////////////////////////////////////////////////////////
1093 // but a "view file" function will be made, probably as a separate page
1094 // links in csv file
1095 // <a href='edi_view_file.php?key=filename' target='_blank'>filename</a>
1096 // OpenEMR open log link: <a href='../../library/freeb/process_bills.log' target='_blank' class='link_submit' title=''>[View Log]</a>
1098 if( headers_sent() ) {
1099 csv_edihist_log("csv_download_file: error headers already sent");
1100 return FALSE;
1102 //FILTER_SANITIZE_URL
1103 //$filename = $_GET['dlkey'];
1105 $filename = filter_input(INPUT_GET,'dlkey',FILTER_SANITIZE_STRING);
1106 $fp = csv_check_filepath($filename);
1107 if (!fp) {
1108 csv_edihist_log("csv_download_file: invalid filename for download $filename");
1109 //echo "csv_download_file: invalid filename for download $filename <br />" . PHP_EOL;
1110 return FALSE; // no -- httpd error code 504
1113 $file_html = csv_filetohtml($fp);
1115 if ($file_html) {
1116 $bn = basename($filename) . '.html';
1117 $ctype="text/html";
1118 $host = $_SERVER['HTTP_HOST'];
1119 $uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
1120 header("Location: http://$host$uri/$extra");
1121 file_put_contents($host . $uri .DIRECTROY_SEPARATOR. $bn, $file_html);
1122 $fsize = filesize($host . $uri .DIRECTROY_SEPARATOR. $bn);
1125 } else {
1126 csv_edihist_log("csv_download_file: file was not converted to html $filename");
1127 //echo "csv_download_file: file was not converted to html $filename <br />" . PHP_EOL;
1128 return FALSE;
1131 // Required for some browsers
1132 if(ini_get('zlib.output_compression'))
1133 ini_set('zlib.output_compression', 'Off');
1136 $ctype="application/pdf";
1138 header("Pragma: public"); // required
1139 header("Expires: 0");
1140 header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
1141 header("Cache-Control: private",false); // required for certain browsers
1142 header("Content-Type: $ctype");
1143 header("Content-Disposition: attachment; filename=\"".basename($fp)."\";" );
1144 header("Content-Transfer-Encoding: binary");
1145 header("Content-Length: ".$fsize);
1146 ob_clean();
1147 flush();
1148 readfile( $fp );
1150 exit();
1156 * check the csv claims tables and return rows for a particular encounter
1158 * @uses csv_pid_enctr_parse()
1159 * @uses csv_file_with_pid_enctr()
1160 * @uses csv_table_select_list()
1161 * @uses csv_search_record()
1162 * @param string encounter number
1163 * @return string
1165 function csv_claim_history($encounter) {
1167 if ($encounter) {
1168 $enct = csv_pid_enctr_parse(strval($encounter));
1169 $e = ($enct) ? $enct['enctr'] : false;
1172 if (!$e) {
1173 return "invalid encounter value $encounter <br />".PHP_EOL;
1175 // get complete pid-encounter from the batch claims record
1176 $efp = csv_file_with_pid_enctr($e);
1177 if (is_array($efp) && count($efp)) {
1178 $pe = $efp[0][0];
1179 } else {
1180 csv_edihist_log("csv_claim_history: failed to locate $e in batch claims record");
1181 return "failed to locate $e in batch claims record";
1183 // use function csv_table_select_list() so that only
1184 // existing csv tables are queried
1185 $tbl2 = csv_table_select_list('array');
1186 $rtypes = array();
1187 if (is_array($tbl2) && count($tbl2) ) {
1188 foreach($tbl2 as $tbl) {
1189 $tp1 = explode(' ', $tbl['desc']);
1190 if ($tp1[1] == 'files') { continue; }
1191 if ($tp1[0] == '999' || $tp1[0] == '997' || $tp1[0] == '277') {
1192 $k = 'f'.$tp1[0];
1193 $rtypes[$k] = $k;
1194 } elseif ($tp1[0] == 'ibr' || $tp1[0] == 'ebr' || $tp1[0] == 'dpr') {
1195 $k = $tp1[0];
1196 $rtypes['prop'][] = $k;
1197 } else {
1198 $k = $tp1[0];
1199 $rtypes[$k] = $k;
1202 } else {
1203 csv_edihist_log("csv_claim_history: failed to get csv table names");
1204 return "failed to get csv table names";
1207 $ch_html .= "<table class='clmhist' columns=4><caption>Encounter Record for $pe</caption>";
1208 $ch_html .= "<tbody>".PHP_EOL;
1210 if (isset($rtypes['batch'])) {
1211 $tp = 'batch';
1212 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1213 $btar = csv_search_record($tp, 'claim', $srchar, '2');
1215 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1216 $ch_html .= "<td>Name</td><td>SvcDate</td><td>CLM01</td><td>File</td>".PHP_EOL;
1217 $ch_html .= "</tr>".PHP_EOL;
1218 if (is_array($btar) && count($btar)) {
1219 foreach($btar as $ch) {
1220 $dt = substr($ch[1], 0, 4).'-'.substr($ch[1], 4, 2).'-'.substr($ch[1], 6, 2);
1221 //array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' );
1222 $ch_html .= "<tr class='chbatch'>".PHP_EOL;
1224 $ch_html .= "<td>{$ch[0]}</td>".PHP_EOL;
1225 $ch_html .= "<td>$dt</td>".PHP_EOL;
1226 $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;
1227 $ch_html .= "<td title='{$ch[4]}'><a target='_blank' href='edi_history_main.php?fvkey={$ch[5]}'>{$ch[5]}</a></td>".PHP_EOL;
1229 $ch_html .= "</tr>".PHP_EOL;
1231 } else {
1232 $ch_html .= "<tr class='chbatch'>".PHP_EOL;
1233 $ch_html .= "<td colspan=4>Batch -- Nothing found for $pe in $tp record</td>".PHP_EOL;
1234 $ch_html .= "</tr>".PHP_EOL;
1238 if (isset($rtypes['f997'])) {
1239 $tp = 'f997';
1240 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1241 $f997ar = csv_search_record($tp, 'claim', $srchar, '2');
1243 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1244 $ch_html .= "<td>Response</td><td>Status</td><td>File</td><td>Notes</td>".PHP_EOL;
1245 $ch_html .= "</tr>".PHP_EOL;
1246 if (is_array($f997ar) && count($f997ar)) {
1247 foreach($f997ar as $ch) {
1249 $msg = strlen($ch[7]) ? $ch[7] : 'ST Number';
1250 //array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'File_997', 'Ctn_837', 'err_seg');
1251 $ch_html .= "<tr class='chf997'>";
1252 $ch_html .= "<td>997/999</td>".PHP_EOL;
1253 $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;
1254 $ch_html .= "<td><a target='_blank' href='edi_history_main.php?fvkey={$ch[5]}'>{$ch[5]}</a></td>".PHP_EOL;
1255 $ch_html .= "<td title='$msg'>{$ch[6]} {$ch[4]}</td>".PHP_EOL;
1256 $ch_html .= "</tr>".PHP_EOL;
1258 } else {
1259 $ch_html .= "<tr class='chf997'>";
1260 $ch_html .= "<td colspan=4>x12 999 -- Nothing found for $pe</td>".PHP_EOL;
1261 $ch_html .= "</tr>".PHP_EOL;
1265 if (isset($rtypes['f277'])) {
1266 $tp = 'f277';
1268 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1269 $f277ar = csv_search_record($tp, 'claim', $srchar, '2');
1271 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1272 $ch_html .= "<td>Response</td><td>Status</td><td>File</td><td>ClaimID</td>".PHP_EOL;
1273 $ch_html .= "</tr>".PHP_EOL;
1274 if (is_array($f277ar) && count($f277ar)) {
1275 foreach($f277ar as $ch) {
1276 // array('PtName', 'SvcDate', 'clm01', 'Status', 'st_277', 'File_277', 'payer_name', 'claim_id', 'bht03_837');
1277 $ch_html .= "<tr class='chf277'>";
1279 $ch_html .= "<td>x12 277</td>".PHP_EOL;
1280 $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;
1281 $ch_html .= "<td title='{$ch[5]}'><a target='_blank' href='edi_history_main.php?fvkey={$ch[5]}'>File</a></td>".PHP_EOL;
1282 $ch_html .= "<td title='{$ch[6]}'>{$ch[7]}</td>".PHP_EOL;
1284 $ch_html .= "</tr>".PHP_EOL;
1286 } else {
1287 $ch_html .= "<tr class='chf277'>";
1288 $ch_html .= "<td colspan=4>x12 277 -- Nothing found for $pe</td>".PHP_EOL;
1289 $ch_html .= "</tr>".PHP_EOL;
1293 if (is_array($rtypes['prop']) && count($rtypes['prop']) ) {
1294 foreach($rtypes['prop'] as $tp) {
1296 $rspnm = strtoupper($tp);
1297 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1298 $ibrar = csv_search_record($tp, 'claim', $srchar, '2');
1300 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1301 $ch_html .= "<td>Response</td><td>Status</td><td>File</td><td>Payer</td>".PHP_EOL;
1302 $ch_html .= "</tr>".PHP_EOL;
1303 if (is_array($ibrar) && count($ibrar)) {
1304 foreach($ibrar as $ch) {
1305 //array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1306 $ch_html .= "<tr class='ch$tp'>";
1308 $ch_html .= "<td>$rspnm</td>".PHP_EOL;
1309 if ($tp == 'dpr') {
1310 $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;
1311 } else {
1312 $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;
1314 $ch_html .= "<td title='{$ch[5]}'><a target='_blank' href='edi_history_main.php?fvkey={$ch[5]}'>File</a></td>".PHP_EOL;
1315 $ch_html .= "<td>{$ch[6]}</td>".PHP_EOL;
1317 $ch_html .= "</tr>".PHP_EOL;
1319 } else {
1320 $ch_html .= "<tr class='ch$tp'>";
1321 $ch_html .= "<td colspan=4>$rspnm -- Nothing found for $pe</td>".PHP_EOL;
1322 $ch_html .= "</tr>".PHP_EOL;
1327 if (isset($rtypes['era'])) {
1328 $tp = 'era';
1330 $srchar = array('s_val'=>$pe, 's_col'=>2, 'r_cols'=>'all');
1331 $eraar = csv_search_record($tp, 'claim', $srchar, '2');
1333 $ch_html .= "<tr class='chhead'>".PHP_EOL;
1334 $ch_html .= "<td>Response</td><td>Status</td><td>Trace</td><td>Payer</td>".PHP_EOL;
1335 $ch_html .= "</tr>".PHP_EOL;
1336 if (is_array($eraar) && count($eraar)) {
1337 foreach($eraar as $ch) {
1339 $msg = $ch[6] .' '.$ch[7].' '.$ch[8];
1340 // array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
1341 $ch_html .= "<tr class='ch835'>";
1343 $ch_html .= "<td>x12 ERA</td>".PHP_EOL;
1344 $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;
1345 $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;
1346 $ch_html .= "<td title=$msg>{$ch[9]}</td>".PHP_EOL;
1348 $ch_html .= "</tr>".PHP_EOL;
1350 } else {
1351 $ch_html .= "<tr class='ch835'>";
1352 $ch_html .= "<td colspan=4>x12 835 ERA -- Nothing found for $pe</td>".PHP_EOL;
1353 $ch_html .= "</tr>".PHP_EOL;
1356 } // end if($tp ...
1357 // -- this is where a query on the payments datatable could be used to show if payment
1358 // has been received, even if no era file shows the payment.
1360 $ch_html .= "</tbody>".PHP_EOL;
1361 $ch_html .= "</table>".PHP_EOL;
1363 return $ch_html;
1368 * Render one of our csv record files as an html table
1370 * This function determines the actual csv file from the file_type and the
1371 * csv_type. A percentage (default=100) of the csv file rows is selected
1372 * or the date field of each row is checked against the optional date parameters.
1374 * @uses csv_parameters()
1375 * @param string $file_type -- one of "|era|f997|ibr|ebr|dpr|f277|batch"
1376 * @param string $csv_type -- either "file" or "claim"
1377 * @param float $row_pct the percentage of the table to return (most recent)
1378 * @param string $datestart
1379 * @param string $dateend
1380 * @return string
1382 function csv_to_html($file_type, $csv_type, $row_pct = 1, $datestart='', $dateend='') {
1384 // read a csv file into an html table, using predefined stylesheet and javascript
1386 $csv_html = "";
1387 $is_date = FALSE;
1389 if (! strpos("|era|f997|ibr|ebr|dpr|f277|batch|ack|ta1", $file_type) ) {
1390 csv_edihist_log("csv_to_html error: incorrect file type $file_type");
1391 $csv_html .= "csv_to_html error: incorrect file type $file_type <br />".PHP_EOL;
1392 return FALSE;
1395 $params = csv_parameters($file_type);
1397 // csv tables date is given date or mtime in col 0 for file, date is service date in col 1 for claim
1398 $dtcol = ($csv_type == "file") ? $params['datecolumn'] : '1';
1399 $fncol = ($csv_type == "file") ? $params['fncolumn'] : '1';
1400 // but dpr files are only in the claims table, a special case
1401 if ($file_type == "dpr") { $fncol = $params['fncolumn']; }
1403 if ($csv_type == "claim") {
1404 $fp = $params['claims_csv'];
1405 } elseif ($csv_type == "file") {
1406 $fp = $params['files_csv'];
1407 } else {
1408 csv_edihist_log("csv_to_html error: incorrect csv type $csv_type");
1409 $csv_html .= "csv_to_html error: incorrect csv type $csv_type <br />".PHP_EOL;
1410 return FALSE;
1412 // for using OpenEMR dynarch calendar, assume format of CCYY-MM-DD
1413 if (preg_match('/\d{4}-\d{2}-\d{2}/', $datestart) && preg_match('/\d{4}-\d{2}-\d{2}/', $dateend) ) {
1414 $ds = str_replace('-', '', $datestart);
1415 $de = str_replace('-', '', $dateend);
1416 if ( $de <= $ds) { $de = date("Ymd", time()); }
1417 $is_date = TRUE;
1418 $row_pct = 1;
1421 $f_name = basename($fp);
1422 // open the file for read and read it into an array
1423 $fh = fopen($fp, "r");
1424 if ($is_date) {
1425 $isok = FALSE;
1426 $idx = 0;
1428 if ($fh !== FALSE) {
1429 while (($data = fgetcsv($fh, 1024, ",")) !== FALSE) {
1431 if ($idx == 0) {
1432 $csv_d[] = $data;
1433 } else {
1434 $isok = (substr($data[$dtcol], 0, 8) >= $ds) ? TRUE : FALSE;
1435 $isok = (substr($data[$dtcol], 0, 8) > $de) ? FALSE : $isok;
1437 if ($isok) { $csv_d[] = $data; }
1439 $idx++;
1441 fclose($fh);
1442 } else {
1443 $csv_html .= "csv_to_html: failed to open $fp <br />".PHP_EOL;
1444 return $csv_html;
1446 } else {
1447 // get the entire table
1448 if ($fh !== FALSE) {
1449 while (($data = fgetcsv($fh)) !== FALSE) {
1450 $csv_d[] = $data;
1452 fclose($fh);
1453 } else {
1454 $csv_html .= "csv_to_html: failed to open $fp <br />".PHP_EOL;
1455 return $csv_html;
1459 $ln_ct = count($csv_d);
1460 // make sure row_pct is between 0 and 1
1461 if ($row_pct > 1 || $row_pct <= 0) { $row_pct = 1; }
1462 // only return the number of desired rows
1463 $rwct = (int)($ln_ct * $row_pct) + 1;
1464 $rwst = $ln_ct - $rwct;
1465 if ($rwst < 1) { $rwst = 1; $rwct = $ln_ct; }
1467 if ($is_date) {
1468 $csv_html .= "<div id='dttl'>Table: $f_name &nbsp;&nbsp; Start Date: $datestart &nbsp; End Date: $dateend &nbsp;Rows: $rwct</div>".PHP_EOL;
1469 } else {
1470 $csv_html .= "<div id='dttl'>Table: $f_name &nbsp;&nbsp; Rows: $ln_ct &nbsp;&nbsp; Shown: $rwct</div>".PHP_EOL;
1473 $csv_html .= "<table id=\"csvTable\" class=\"csvDisplay\">".PHP_EOL;
1474 // this is the body of the table
1476 if ($csv_type == 'file') {
1477 //['era']['file'] = array('Date', 'FileName', 'Trace', 'claim_ct', 'Denied', 'Payer');
1479 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1480 foreach ($csv_d[0] as $h) { $csv_html .= "<th>$h</th>"; }
1481 $csv_html .= PHP_EOL.'</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1483 if ($file_type == 'era') {
1484 for ($i=$rwst; $i<$ln_ct; $i++) {
1485 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1486 $csv_html .= "<tr class='{$bgc}'>".PHP_EOL;
1487 foreach($csv_d[$i] as $idx=>$dta) {
1488 if ($idx == 1) {
1489 $csv_html .= "<td><a href='edi_history_main.php?fvkey=$dta' target='_blank'>$dta</a></td>".PHP_EOL;
1490 } elseif ($idx == 2) {
1491 $fnm = $csv_d[$i][1];
1492 $csv_html .= "<td><a href='edi_history_main.php?erafn=$fnm&trace=$dta' target='_blank'>$dta</a> &nbsp;&nbsp;<a class=\"clmstatus\" target='_blank' href='edi_history_main.php?tracecheck=$dta&ckprocessed=yes'>(a)</td>".PHP_EOL;
1493 } else {
1494 $csv_html .= "<td>$dta</td>".PHP_EOL;
1497 $csv_html .= "</tr>".PHP_EOL;
1499 } elseif ($file_type == 'f997') {
1501 // array('Date', 'FileName', 'Ctn_999', 'ta1ctrl', 'RejCt');
1502 for ($i=$rwst; $i<$ln_ct; $i++) {
1503 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1504 $csv_html .= "<tr class=\"$bgc\">".PHP_EOL;
1506 foreach($csv_d[$i] as $idx => $dta) {
1507 if ($idx == 1) {
1508 $fnm = $dta;
1509 $ext = strtolower(substr($dta, -3));
1510 $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;
1511 } elseif ($idx == 3) {
1512 $csv_html .= "<td><a target=\"_blank\" href=\"edi_history_main.php?btctln=$dta\">$dta</a></td>".PHP_EOL;
1513 } elseif ($idx == 4) {
1514 if ($ext == '999' || $ext == '997') {
1515 $csv_html .= "<td><a class=\"codeval\" target=\"_blank\" href=\"edi_history_main.php?fv997=$fnm&err997=$dta\">$dta</a></td>".PHP_EOL;
1516 } elseif ($ext == 'ta1' || $ext == 'ack') {
1517 $csv_html .= "<td><a class=\"codeval\" target=\"_blank\" href=\"edi_history_main.php?ackfile=$fnm&ackcode=$dta\">$dta</a></td>".PHP_EOL;
1518 } else {
1519 $csv_html .= "<td>$dta</td>".PHP_EOL;
1521 } else {
1522 $csv_html .= "<td>$dta</td>".PHP_EOL;
1525 $csv_html .= "</tr>".PHP_EOL;
1527 } elseif ($file_type == 'ebr' || $file_type == 'ibr' ) {
1528 //['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
1529 for ($i=$rwst; $i<$ln_ct; $i++) {
1530 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1531 $fnm = $csv_d[$i][1];
1532 $btfile = $csv_d[$i][5];
1533 if (intval($csv_d[$i][4]) > 0) {
1534 $rejlink = "<td><a class=\"clmstatus\" target=\"_blank\" href=\"edi_history_main.php?ebrfile=$fnm&ebrclm=any\">{$csv_d[$i][4]}</a></td>";
1535 } else {
1536 $rejlink = "<td>{$csv_d[$i][4]}</td>";
1539 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1540 $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;
1541 $csv_html .= "<td>{$csv_d[$i][2]}</td>".PHP_EOL;
1542 $csv_html .= "<td>{$csv_d[$i][3]}</td>".PHP_EOL;
1543 $csv_html .= $rejlink.PHP_EOL;
1544 $csv_html .= "<td><a target=\"_blank\" href=\"edi_history_main.php?fvkey=$btfile\">$btfile</a></td>".PHP_EOL;
1546 $csv_html .= "</tr>".PHP_EOL;
1548 } elseif ($file_type == 'batch') {
1549 //['batch']['file'] = array('Date', 'FileName', 'Ctn_837', 'claim_ct', 'x12_partner');
1550 for ($i=$rwst; $i<$ln_ct; $i++) {
1551 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1552 $csv_html .= "<tr class='{$bgc}'>";
1553 foreach($csv_d[$i] as $idx=>$dta) {
1554 $fnm = $csv_d[$i][$fncol];
1555 if ($idx == $fncol) {
1556 $csv_html .= "<td><a href='edi_history_main.php?fvkey=$dta' target='_blank'>$dta</a></td>".PHP_EOL;
1557 } elseif ($idx == 2) {
1558 // batch control number
1559 $csv_html .= "<td>$dta &nbsp;&nbsp;<a class=\"clmstatus\" target=\"_blank\" href=\"edi_history_main.php?batchicn=$dta\">(r)</a></td>";
1560 } else {
1561 $csv_html .= "<td>$dta</td>".PHP_EOL;
1564 $csv_html .= "</tr>".PHP_EOL;
1566 } else {
1567 // the generic case -- for 'file' type tables, the filename is in column 1, as set in the parameters array
1568 // see csv_parameters()
1569 for ($i=$rwst; $i<$ln_ct; $i++) {
1570 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1571 $csv_html .= "<tr class='{$bgc}'>";
1572 foreach($csv_d[$i] as $idx=>$dta) {
1573 if ($idx == $fncol) {
1574 $csv_html .= "<td><a href='edi_history_main.php?fvkey=$dta' target='_blank'>$dta</a></td>".PHP_EOL;
1575 } else {
1576 $csv_html .= "<td>$dta</td>".PHP_EOL;
1579 $csv_html .= "</tr>".PHP_EOL;
1582 } elseif ($csv_type == 'claim') {
1583 // a 'claim' type table $csv_type == 'claim' there is more variation
1584 if ($file_type == 'era') {
1585 // era csv_type is claim col 2 is pid, 3 encounter, 8 is trace
1586 //['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
1587 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1588 $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;
1589 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1590 for ($i=$rwst; $i<$ln_ct; $i++) {
1591 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1592 $nm = $csv_d[$i][0];
1593 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1594 $clm = $csv_d[$i][2];
1595 $sts = $csv_d[$i][3];
1596 $trc = $csv_d[$i][4];
1597 $fnm = $csv_d[$i][5];
1598 $clmid = $csv_d[$i][6];
1599 $msg = $csv_d[$i][6] .' '.$csv_d[$i][7].' '.$csv_d[$i][8];
1600 $pr = $csv_d[$i][9];
1601 $csv_html .= "<tr class='{$bgc}'>";
1602 //Name
1603 $csv_html .= "<td>$nm</td>".PHP_EOL;
1604 //SvcDate
1605 $csv_html .= "<td>$dt</td>".PHP_EOL;
1606 //CLM01
1607 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=&btpid=$clm'>$clm</a></td>".PHP_EOL;
1608 //Status
1609 $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;
1610 //Trace
1611 $csv_html .= "<td><a target='_blank' href='edi_history_main.php?erafn=$fnm&trace=$trc&srchtp=trace'>$trc</a></td>".PHP_EOL;
1612 //File_835
1613 $csv_html .= "<td title=$fnm><a target='_blank' href='edi_history_main.php?fvkey=$fnm'>x12</a></td>".PHP_EOL;
1614 //ClaimID
1615 $csv_html .= "<td title=$msg>$pr</td>".PHP_EOL;
1617 $csv_html .= "</tr>".PHP_EOL;
1619 } elseif ($file_type == 'dpr') {
1620 // dpr case, only file type is claim,
1621 //['dpr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1622 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1623 $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;
1624 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1625 for ($i=$rwst; $i<$ln_ct; $i++) {
1626 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1627 $pidenc = $csv_d[$i][2];
1628 $btfile = $csv_d[$i][4];
1629 $fnm = $csv_d[$i][5];
1630 //$msg = $csv_d[$i][8];
1631 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1632 $csv_html .= "<tr class='{$bgc}'>";
1633 //Name
1634 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1635 //SvcDate
1636 $csv_html .= "<td>$dt</td>".PHP_EOL;
1637 //CLM01
1638 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=$btfile&btpid=$pidenc'>$pidenc</a></td>".PHP_EOL;
1639 //Status
1640 $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;
1641 //Batch
1642 $csv_html .= "<td title=$btfile><a target='_blank'href='edi_history_main.php?fvkey=$btfile'>Batch</a></td>".PHP_EOL;
1643 //File
1644 $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;
1645 //Payer
1646 $csv_html .= "<td>{$csv_d[$i][6]}</td>".PHP_EOL;
1648 $csv_html .= "</tr>".PHP_EOL;
1651 } elseif ($file_type == 'ebr' || $file_type == 'ibr') {
1652 //array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1654 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1655 $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;
1656 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1657 for ($i=$rwst; $i<$ln_ct; $i++) {
1658 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1660 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1661 $pidenc = $csv_d[$i][2];
1662 $btfile = $csv_d[$i][4];
1663 $fnm = $csv_d[$i][5];
1664 //$msg = $csv_d[$i][8];
1665 $csv_html .= "<tr class='{$bgc}'>";
1666 //Name
1667 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1668 //SvcDate
1669 $csv_html .= "<td>$dt</td>".PHP_EOL;
1670 //CLM01
1671 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=$btfile&btpid=$pidenc'>$pidenc</a></td>".PHP_EOL;
1672 //Status
1673 $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;
1674 //Batch
1675 $csv_html .= "<td title=$btfile><a target='_blank' href='edi_history_main.php?fvkey=$btfile'>Batch</a></td>".PHP_EOL;
1676 //File
1677 $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;
1678 //Payer
1679 $csv_html .= "<td title=$msg>{$csv_d[$i][6]}</td>".PHP_EOL;
1681 $csv_html .= "</tr>".PHP_EOL;
1683 } elseif ($file_type == 'f997') {
1685 //['f997']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'File_997', 'Ctn_837', 'err_seg');
1686 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1687 $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;
1688 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1689 for ($i=$rwst; $i<$ln_ct; $i++) {
1690 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1692 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1693 $pidenc = $csv_d[$i][2];
1694 $akn = $csv_d[$i][4];
1695 $fnm = $csv_d[$i][5];
1696 $msg = strlen($csv_d[$i][7]) ? $csv_d[$i][7] : 'ST Number';
1698 $csv_html .= "<tr class='{$bgc}'>";
1699 //Name
1700 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1701 //SvcDate
1702 $csv_html .= "<td>$dt</td>".PHP_EOL;
1703 //CLM01
1704 $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;
1705 //Status
1706 $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;
1707 //ST
1708 $csv_html .= "<td title='$msg'>$akn</td>".PHP_EOL;
1709 //File
1710 $csv_html .= "<td><a target='_blank' href='edi_history_main.php?fvkey=$fnm'>$fnm</a></td>".PHP_EOL;
1711 //Batch
1712 $csv_html .= "<td><a target='_blank' href='edi_history_main.php?btctln={$csv_d[$i][6]}'>{$csv_d[$i][6]}</a></td>".PHP_EOL;
1714 $csv_html .= "</tr>".PHP_EOL;
1716 } elseif ($file_type == 'f277') {
1718 //['f277']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_277', 'File_277', 'payer_name', 'claim_id', 'bht03_837');
1719 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1720 $csv_html .= '<th>Name</th><th>SvcDate</th><th>CLM01</th><th>Status</th><th>File_277</th><th>ClaimID</th>'.PHP_EOL; //<th>Batch</th>
1721 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1722 for ($i=$rwst; $i<$ln_ct; $i++) {
1723 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1724 $csv_html .= "<tr class='{$bgc}'>";
1726 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1727 $btpid = $csv_d[$i][2];
1728 $f277file = $csv_d[$i][5];
1729 $clmid = $csv_d[$i][7];
1730 $bt_bht03 = $csv_d[$i][8];
1731 //$msg277 = (strlen($csv_d[$i][9])) ? $csv_d[$i][9] : '';
1732 //Name
1733 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1734 //SvcDate
1735 $csv_html .= "<td>$dt</td>".PHP_EOL;
1736 //CLM01
1737 $csv_html .= "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=$bt_bht03&btpid=$btpid'>$btpid</a></td>".PHP_EOL;
1738 //Status
1739 $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;
1740 //File
1741 $csv_html .= "<td title='$f277file'><a target='_blank' href='edi_history_main.php?fvkey=$f277file'>File</a></td>".PHP_EOL;
1742 //ClaimID
1743 $csv_html .= "<td>$clmid</td>".PHP_EOL;
1744 //Batch
1745 //$csv_html .= "<td title='$bt_bht03'><a target='_blank' href='edi_history_main.php?btctln=$bt_bht03'>Batch</a></td>".PHP_EOL;
1747 $csv_html .= "</tr>".PHP_EOL;
1749 } elseif ($file_type == 'batch') {
1751 //['batch']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' );
1752 $csv_html .= '<thead>'.PHP_EOL.'<tr>'.PHP_EOL;
1753 $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;
1754 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1755 for ($i=$rwst; $i<$ln_ct; $i++) {
1756 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1758 $dt = substr($csv_d[$i][1], 0, 4).'-'.substr($csv_d[$i][1], 4, 2).'-'.substr($csv_d[$i][1], 6, 2);
1759 $msg = $csv_d[$i][6].' ('.$csv_d[$i][7].')';
1760 $btfile = $csv_d[$i][5];
1762 $csv_html .= "<tr class='{$bgc}'>";
1763 //Name
1764 $csv_html .= "<td>{$csv_d[$i][0]}</td>".PHP_EOL;
1765 //SvcDate
1766 $csv_html .= "<td>$dt</td>".PHP_EOL;
1767 //CLM01
1768 $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;
1769 //Ins
1770 $csv_html .= "<td>{$csv_d[$i][3]}</td>".PHP_EOL;
1771 //Fee/PtPd
1772 $csv_html .= "<td>$msg</td>".PHP_EOL;
1773 //File
1774 $csv_html .= "<td><a target='_blank' href='edi_history_main.php?fvkey=$btfile'>$btfile</a></td>".PHP_EOL;
1775 //Provider
1776 $csv_html .= "<td>{$csv_d[$i][8]}</td>".PHP_EOL;
1778 $csv_html .= "</tr>".PHP_EOL;
1780 } else {
1782 $csv_html .= '<thead>'.PHP_EOL.'<tr>';
1783 foreach ($csv_d[0] as $h) { $csv_html .= "<th>$h</th>"; }
1784 $csv_html .= '</tr>'.PHP_EOL.'</thead>'.PHP_EOL.'<tbody>'.PHP_EOL;
1786 for ($i=$rwst; $i<$ln_ct; $i++) {
1787 $bgc = ($i % 2 == 1 ) ? 'odd' : 'even';
1788 $csv_html .= "<tr class='{$bgc}'>";
1789 foreach($csv_d[$i] as $idx=>$dta) {
1790 $csv_html .= "<td>$dta</td>".PHP_EOL;
1792 $csv_html .= "</tr>".PHP_EOL;
1795 } // end body of the table
1796 //$csv_html .= "</tbody>".PHP_EOL."</table>".PHP_EOL."</div>".PHP_EOL;
1797 $csv_html .= "</tbody>".PHP_EOL."</table>".PHP_EOL;
1798 return $csv_html;
1802 * Produce an html rendition of one of our files
1804 * this function accepts a file name array from uploads.php, one file only,
1805 * or a string file path as the filepath argument
1807 * @uses csv_check_filepath()
1808 * @uses csv_parameters()
1809 * @uses csv_x12_segments()
1810 * @param mixed $filepath string or array path to or name of one of our files
1811 * @return string html formatted
1813 function csv_filetohtml ($filepath) {
1815 if (is_array($filepath) && count($filepath) > 0) {
1816 $ftkey = array_keys($filepath);
1817 $type = $ftkey[0];
1818 $ftestpath = $filepath[$type][0];
1819 $bn = basename($ftestpath);
1820 } else {
1821 $bn = basename($filepath);
1822 $params = csv_parameters("ALL");
1824 foreach($params as $ky=>$val) {
1825 if ( !$params[$ky]['regex'] ) { continue; }
1826 if (!preg_match($params[$ky]['regex'], $bn) ) {
1827 continue;
1828 } else {
1829 $type = $params[$ky]['type'];
1830 //$ftestpath = dirname(__FILE__) . $params[$ky]['directory'].DIRECTORY_SEPARATOR.$bn;
1831 $ftestpath = $params[$ky]['directory'].DIRECTORY_SEPARATOR.$bn;
1832 break;
1838 if (!isset($type) ) {
1839 csv_edihist_log("csv_filetohtml: failed to type $bn");
1840 $out_str = "csv_filetohtml: failed to classify $bn <br />". PHP_EOL;
1841 return $out_str;
1844 $fp = csv_check_filepath($ftestpath, $type);
1845 if (!$fp) {
1846 csv_edihist_log("csv_filetohtml: could not get good path for $bn");
1847 $out_str = "csv_filetohtml: could not get good path for $bn <br />". PHP_EOL;
1848 return $out_str;
1850 // different file types
1851 if (strpos("|batch|era|f277|f997|999|837|835|ta1", (string)$type) ) {
1852 // x12 file types
1853 $seg_ar = csv_x12_segments($fp, $type);
1854 if (is_array($seg_ar) && count($seg_ar['segments']) ) {
1855 $txt_ar = $seg_ar['segments'];
1856 $seg_d = $seg_ar['delimiters']['t'];
1857 $fltp = 'x12';
1858 } else {
1859 csv_edihist_log("csv_filetohtml: did not get segments for $bn");
1860 $out_str = "csv_filetohtml: did not get segments for $bn <br />". PHP_EOL;
1861 return $out_str;
1864 } elseif (strpos("|ibr|ebr|dpr|ack", $type) ) {
1865 // clearinghouse file types (newlines)
1866 $fh = fopen($fp, 'r');
1867 if ($fh) {
1868 while (($buffer = fgets($fh, 1024)) !== false) {
1869 $txt_ar[] = $buffer;
1871 if (!feof($fh)) {
1872 csv_edihist_log("csv_filetohtml: failed to open $bn <br />");
1874 fclose($fh);
1875 $fltp = 'ibr';
1876 } else {
1877 csv_edihist_log("csv_filetohtml: did not get lines for $bn");
1878 $out_str = "csv_filetohtml: did not get lines for $bn <br />". PHP_EOL;
1879 return $out_str;
1881 } elseif (strpos("|text", $type) ) {
1882 // clearinghouse readable versions
1883 $txt_ar = file_get_contents($fp);
1884 $fltp = 'text';
1885 if (!$txt_ar) {
1886 csv_edihist_log("csv_filetohtml: did not get contents for $bn");
1887 $out_str = "csv_filetohtml: did not get contents for $bn <br />". PHP_EOL;
1888 return $out_str;
1891 } else {
1892 csv_edihist_log("csv_filetohtml: $type was not matched for $bn");
1893 $out_str = "csv_filetohtml: $type was not matched for $bn <br />". PHP_EOL;
1894 return $out_str;
1896 // we have navigated our checks and read the file
1897 $out_str = "";
1899 // now prepare the html page
1900 $out_str = '';
1901 // use an ordered list format for x12 and ebr/ibr, regular text for text type
1902 if ($fltp == "text") {
1903 $out_str .= "<h4>$bn</h4>
1904 <div class=\"filetext\">
1905 <pre>
1907 $out_str .= $txt_ar;
1908 $out_str .= "
1909 </pre>";
1910 } elseif ($fltp == 'x12') {
1911 $out_str .= "<h4>$bn</h4>
1912 <div class=\"filetext\">
1913 <ol>
1915 foreach($txt_ar as $line) {
1916 $out_str .= "
1917 <li>
1919 $line$seg_d
1920 </p>
1921 </li>
1924 $out_str .= "</ol>
1926 } else {
1927 $out_str .= "<h4>$bn</h4>
1928 <div class=\"filetext\">
1929 <ol>
1931 foreach($txt_ar as $line) {
1932 $out_str .= "
1933 <li>
1935 $line
1936 </p>
1937 </li>
1940 $out_str .= "</ol>
1944 $out_str .= "
1945 </div>
1946 </body>
1947 </html>";
1949 return $out_str;
1953 * Check that the file path we are working with is a readable file.
1955 * If it is a file we have uploaded and we have only the file name
1956 * this function will type the file and find it in the uploaded files directories
1957 * and return the complete path.
1959 * @uses csv_parameters()
1960 * @param string $filename name of a file that is one of our types
1961 * @param string $type optional; one of our file types
1962 * @return string either an empty string or a readable filepath
1964 function csv_check_filepath($filename, $type = "ALL") {
1966 // if file is readable, just return it
1967 if ( is_file($filename) && is_readable($filename) ) {
1968 return $filename;
1971 $goodpath = "";
1972 $fn = basename($filename);
1974 if ($type != "ALL") {
1975 $p = csv_parameters($type);
1976 if (is_array($p) && array_key_exists('type', $p) ) {
1977 // type was found
1978 $fp = $p['directory'].DIRECTORY_SEPARATOR.$fn;
1979 if ( is_file($fp) && is_readable($fp) ) {
1980 $goodpath = $fp;
1982 } else {
1983 csv_edihist_log("csv_check_filepath: invalid type $type");
1985 } else {
1986 $p_ar = csv_parameters("ALL");
1987 foreach ($p_ar as $tp=>$par) {
1989 if ( !$p_ar[$tp]['regex'] || !preg_match($p_ar[$tp]['regex'], $fn) ) {
1990 continue;
1991 } else {
1993 $fp = $p_ar[$tp]['directory'].DIRECTORY_SEPARATOR.$fn;
1994 if ( is_file($fp) && is_readable($fp) ) {
1995 $goodpath = $fp;
1997 break;
2002 return $goodpath;
2006 * Verify readibility and check for some expected content.
2008 * @uses csv_check_filepath()
2009 * @param string $file_path full path to file
2010 * @param string $type one of our file types
2011 * @param bool $val_array optional; default is false, whether to return filepath only
2012 * or array(filepath, next_segment)
2013 * @return string|array string file path or array if valid, otherwise 'false'
2015 function csv_verify_file( $file_path, $type, $val_array=FALSE ) {
2016 // check whether $file_path actually leads to a plausible x12 file
2017 // and supply proper directory if needed
2018 $fp = csv_check_filepath($file_path, $type);
2020 // verify that the file is correct format by checking the first ST segment
2021 if ($fp) {
2023 $type = strtolower($type);
2025 if ($type == "batch" || $type == "837") { $st_str = "ST*837"; $next_segment = "GS"; $slen = 250; }
2026 if ($type == "era" || $type == "835") { $st_str = "ST*835"; $next_segment = "GS"; $slen = 250; }
2027 if ($type == "997" || $type == "f997") { $st_str = "ST*997"; $next_segment = "TA1"; $slen = 250; }
2028 if ($type == "999" || $type == "f999") { $st_str = "ST*999"; $next_segment = "TA1"; $slen = 250; }
2029 if ($type == "277" || $type == "f277") { $st_str = "ST*277"; $next_segment = "GS"; $slen = 250; }
2030 if ($type == "271" || $type == "f271") { $st_str = "ST*271"; $next_segment = "GS"; $slen = 250; }
2031 if ($type == "ta1") { $st_str = "TA1*"; $next_segment = "TA1"; $slen = 200; }
2032 // note the ebr, ibr, dpr ack checks will not be valid for years < 2010 or > 2019
2033 if ($type == "ebr") { $st_str = "1|201"; $slen = 10; }
2034 if ($type == "ibr") { $st_str = "1|201"; $slen = 10; }
2035 if ($type == "dpr") { $st_str = "DPR|201"; $slen = 10; }
2036 if ($type == "ack") { $st_str = "1|201"; $slen = 10; }
2037 if ($type == "text") { $st_str = "Date Received"; $slen = 800; }
2038 // for proprietary file formats, if we have a list of clearinghouses,
2039 // the $st_str could be "Availity|Emdeon|ABC " etc. and name probably found in the first 100 characters
2040 // instead of Date Received, which I just guess will be standard and within the first 500 characters
2041 $f_str = file_get_contents($fp, FALSE, NULL, 0, $slen);
2042 $st_pos = strpos($f_str, $st_str);
2044 // special check for 997/999 types, since 999 may be type 997
2045 if (($type == "997" || $type == "f997") && preg_match('/\.999$/', $fp) ) {
2046 $st_pos = strpos($f_str, "ST*999");
2047 $next_segment = "TA1";
2049 //$f_str = file_get_contents($fp);
2050 //$st_pos = strpos(substr($f_str, 0, $slen), $st_str);
2051 if ( $st_pos === FALSE ) {
2052 // did not find the magic word
2053 csv_edihist_log ("csv_verify_file: Error, not a valid $type file: $file_path");
2054 //echo "ibr_era_check_path Error, not a valid $type file: $file_path <br />"
2055 $fp = FALSE;
2056 $next_segment = FALSE;
2058 } else {
2059 $next_segment = '';
2061 if ($val_array) {
2062 return array($fp, $next_segment);
2063 } else {
2064 return $fp;
2069 * Parse Availity ebr, ibr, or dpr file to an array
2071 * This function will return a multidimensional array that is indexed per line of the file
2072 * and another array of each field or element in the line. It uses the
2073 * constant IBR_DELIMITER as the element delimiter
2075 * @param string $file_path complete path to file
2076 * @return array multidimensional array
2078 function csv_ebr_filetoarray ($file_path ) {
2079 // read the ibr, ebr, dpr file into an array of arrays
2080 // since the file is multi-line, use fgets()
2081 //$delim = "|";
2082 $ext = substr($file_path, -3);
2083 $fp = csv_verify_file( $file_path, $ext);
2084 if (!$fp) {
2085 csv_edihist_log("csv_ebr_filetoarray: failed to verify $file_path");
2086 return false;
2088 $ar_ibr = array();
2089 $fh = fopen($fp, 'r');
2090 if ($fh) {
2091 while (($buffer = fgets($fh, 4096)) !== false) {
2092 $ar_ibr[] = explode ( IBR_DELIMITER, trim($buffer) );
2094 } else {
2096 csv_edihist_log( "csv_ebr_filetoarray Error: failed to read " . $file_path );
2098 fclose($fh);
2099 return $ar_ibr;
2104 * Parse an x12 segment text into an array.
2106 * @param string $sgmt_str -- a substring of all or part of a segment
2107 * @param string $seg_term -- segment delimiter default is "~"
2108 * @param string $elem_delim -- element delimiter default is "*"
2109 * @return array -- exploded $sgmt_str by $element_d
2111 function csv_x12_segment_to_array($sgmt_str, $seg_term = "~", $elem_delim = "*") {
2113 // allow for imprecise selecting of segment strings
2115 $slen = strlen($sgmt_str);
2116 if (!$slen) { return FALSE; }
2117 if (!strpos($sgmt_str, $elem_delim)) { return FALSE; }
2118 // find segment delimiters (up to two)
2119 $p1 = strpos($sgmt_str, $seg_term);
2120 $p2 = ($p1) ? strpos($sgmt_str, $seg_term, $p1+1 ) : FALSE;
2121 if ($p1===FALSE) {
2122 // assume we have just the segment text
2123 $seg1 = trim($sgmt_str);
2124 } elseif ($p1 && $p2 && ($p2 > $p1) && ($p2-$p1 < $slen) ) {
2125 // assume we have end of segment ~ segment ~ begining of next segment
2126 $seg1 = substr($sgmt_str, $p1+1, $p2-$p1-1);
2127 } elseif ($p1 !== FALSE && $p1 == 0) {
2128 // assume we have ~segment
2129 $seg1 = substr($sgmt_str, $p1+1);
2130 } elseif ($p1 !== FALSE && $p1+1 == $slen ) {
2131 // assume we have segment~
2132 $seg1 = substr($sgmt_str, 0, $p1-1);
2133 } elseif ($p1 !== FALSE && $p1+1 < $slen ) {
2134 // assume we have segment~ start of segment
2135 $seg1 = substr($sgmt_str, 0, $p1-1);
2136 } else {
2137 // no conjecture matched, just use it
2138 $seg1 = trim($sgmt_str);
2141 $ar_seg = explode ($elem_delim, $seg1 );
2143 return $ar_seg;
2148 * Extract x12 delimiters from the ISA segment
2150 * There are obviously easier/faster ways of doing this, but I wanted to be able
2151 * to possibly extract these needed values from malformed or mishandled
2152 * files, so we go character by character. The array returned is empty on error, otherwise:
2153 * <pre>
2154 * array('t'=>segment terminator, 'e'=>element delimiter,
2155 * 's'=>sub-element delimiter, 'r'=>repetition delimiter)
2156 * </pre>
2158 * @param string $isa_str126 first 126 characters of x12 file
2159 * @param string $next_segid the segment ID immediately following ISA segment
2160 * @return mixed array or false on error
2162 function csv_x12_delimiters($isa_str126, $next_segid) {
2163 // this function reads the ISA segment and into the next segment of
2164 // an x12 file, to determine the delimiters,
2165 // ISA segment is 106 characters, 16 elements, so the $next_segid could be dispensed with
2167 if (substr($isa_str126, 0, 3) != "ISA") {
2168 // not the starting 126 characters
2169 csv_edihist_log("csv_x12_delimiters Error: isa_str126 does not begin with ISA");
2170 return FALSE;
2172 if (! strpos($isa_str126, $next_segid) ) {
2173 // not the starting 126 characters
2174 csv_edihist_log("csv_x12_delimiters Error: next_segment $next_segid not in isa_str126 ");
2175 return FALSE;
2177 // $ns_pos = strpos($isa_str126, $next_segid);
2178 // $elem_pos = strrpos (substr($isa_str126, 0, $ns_pos-1), $elem_delim);
2179 // $dstr = substr($isa_str126, $elem_pos, $ns_pos-1);
2181 $ret_ar = array();
2182 $delim_ct = 0;
2183 $seg2_len = strlen($next_segid); // usually "GS" but could be "TA1" or whatever the specification says
2184 $rep_d = ""; // repetition delimiter ISA11 5010A1 - per Availity EDI guide
2186 $elem_delim = substr($isa_str126, 3, 1); // ISA*
2187 $s = '';
2188 $chars = strlen($isa_str126);
2190 for ($i = 0; $i < $chars; $i++) {
2191 if (strlen($s) >= 3 && substr($s, -$seg2_len) == $next_segid) { break; }
2192 $c = substr($isa_str126, $i, 1);
2193 if ($c == $elem_delim) {
2194 if ($delim_ct == 11) { $rep_d = substr($s, 1, 1); }
2195 $s = $elem_delim;
2196 $delim_ct++;
2197 } else {
2198 $s .= $c;
2200 // there are 16 elements in ISA segment
2201 if ($delim_ct > 16) {
2202 // incorrect $next_segid argument
2203 csv_edihist_log("csv_x12_delimiters: incorrect next_segment $next_segid does not follow ISA");
2204 return FALSE;
2207 if (strlen($s) >= 5) {
2208 $ret_ar["t"] = $s[2];
2209 $ret_ar["e"] = $s[0];
2210 $ret_ar["s"] = $s[1];
2211 $ret_ar["r"] = $rep_d;
2212 } else {
2213 csv_edihist_log("csv_x12_delimiters: Invalid delimiters $s");
2215 return $ret_ar;
2219 * from php help: tleffler [AT] gmail [DOT] com 12-May-2011 08:55
2221 * Not used, but possibly interesting. Thought is to pass a file handle as an argument
2222 * @param mixed $possibleResource
2223 * @return bool
2225 function isResource ($possibleResource) { return !is_null(@get_resource_type($possibleResource)); }
2228 * Parse x12 file into array of segments.
2230 * This function relies on csv_x12_delimiters() to get the delimiters array
2231 * and on csv_verify_file() to supply the next_segment value.
2232 * There are some basic verifications and failures are noted in the log.
2233 * <pre>
2234 * 'path'=>pathtofile
2235 * 'delimiters'=>array from x12_delimiters()
2236 * 'segments'=>segments[i]=>segment text
2237 * if $seg_array is true then segments[i]=>array of elements
2238 * </pre>
2240 * @uses csv_x12_delimiters()
2241 * @uses csv_verify_file()
2242 * @param string $file_path the full path for the file
2243 * @param string $type one of batch|837|era|835|997|999|277|271
2244 * @param bool $seg_array whether each segment should be made into an array of elements
2245 * @return array|bool array['delimiters']['segments']['path'], or false on error
2247 function csv_x12_segments($file_path, $type, $seg_array = FALSE) {
2248 // do verifications
2249 $fp_ar = csv_verify_file($file_path, $type, TRUE );
2251 if (!$fp_ar || !$fp_ar[0]) {
2252 csv_edihist_log ("csv_x12_segments: verification failed for $file_path");
2253 return FALSE;
2256 if ($fp_ar) {
2258 $fp =$fp_ar[0];
2259 $next_segment = $fp_ar[1];
2260 $f_str = file_get_contents($fp);
2262 // verify $delimiters
2263 $delimiters = csv_x12_delimiters(substr($f_str,0,126), $next_segment);
2264 $dlm_ok = FALSE;
2265 // here, expect $delimiters['t'] ['e'] ['s'] ['r']
2266 if (is_array($delimiters) && array_keys($delimiters) == array('t', 'e', 's', 'r') ) {
2267 $dlm_ok = TRUE;
2268 $seg_d = $delimiters['t'];
2269 $elem_d = $delimiters['e'];
2270 $subelem_d = $delimiters['s'];
2271 $rep_d = $delimiters['r'];
2272 } else {
2273 csv_edihist_log ("csv_x12_segments: invalid delimiters or delimiters wrong for $fp");
2274 return FALSE;
2277 // OK, now initialize variables
2278 $fn = basename($fp);
2279 $ar_seg = array();
2280 $ar_seg['path'] = $fp;
2281 $ar_seg['delimiters'] = $delimiters;
2282 $ar_seg['segments'] = array();
2283 $seg_pos = 0; // position where segment begins
2284 $st_segs_ct = 0; // segments in ST-SE envelope
2285 $se_seg_ct = ""; // segment count from SE segment
2286 $isa_segs_ct = 0; // segments in ISA envelope
2287 $isa_ct = 0; // ISA envelope count
2288 $iea_ct = 0; // IEA count
2289 $trnset_seg_ct = 0; // segments by sum of isa segment count
2291 $isa_str = "ISA".$elem_d; // to reduce evaluations
2292 $iea_str = "IEA".$elem_d;
2293 $st_str = "ST".$elem_d;
2294 $se_str = "SE".$elem_d;
2296 $idx = -1;
2297 $moresegs = true;
2298 while ($moresegs) {
2299 $idx++;
2300 // extract each segment from the file text
2301 $seg_end = strpos($f_str, $seg_d, $seg_pos);
2302 $moresegs = strpos($f_str, $seg_d, $seg_end+1);
2304 $seg_text = substr($f_str, $seg_pos, $seg_end-$seg_pos);
2305 $seg_pos = $seg_end + 1;
2307 // we trim in case there are line or carriage returns
2308 $seg_text = trim($seg_text);
2310 // check for non ASCII basic characters. Note reg_ex '/[^\x20-\xFF]/' allows extended ASCII characters
2311 // this is partly file syntax and partly protection -- we don't want to process an imposter
2312 // We are mostly concerned with \x00 to \x19, the "control" characters, but apparently some are allowed
2314 if (preg_match_all('/[^\x20-\x7E]/', $seg_text, $matches)) {
2315 csv_edihist_log ("csv_x12_segments: Non-basic ASCII character in segment ($idx) in file $fn");
2316 // quit here? return false; -- actually files have probably been scanned before in upload.php
2317 // also x12 files have more allowed characters than these
2319 if ($seg_array) {
2320 $ar_seg['segments'][] = explode($elem_d, $seg_text);
2321 } else {
2322 $ar_seg['segments'][] = $seg_text;
2324 $st_segs_ct++;
2325 $isa_segs_ct++;
2327 // some checks, if wanted
2328 if (substr($seg_text, 0, 4) == $isa_str) {
2329 $isa_seg = explode($elem_d, $seg_text);
2330 $isa_id = $isa_seg[13];
2331 $isa_segs_ct = 1;
2332 $isa_ct++;
2333 continue;
2336 if (substr($seg_text, 0, 3) == $st_str) {
2337 // $e = strpos($seg_text, $elem_d, 8); // ST*835* is 7 characters
2338 // $st02 = substr($seg_text, 7, $e-7);
2339 $st_ar = explode($elem_d, $seg_text);
2340 $st_num = $st_ar[2];
2341 $st_segs_ct = 1;
2342 continue;
2344 if (substr($seg_text, 0, 3) == $se_str) {
2345 $se_ar = explode($elem_d, $seg_text);
2346 $se_seg_ct = $se_ar[1];
2347 $se_num = $se_ar[2];
2348 if ($se_num != $st_num) {
2349 csv_edihist_log ("csv_x12_segments: ST-SE number mismatch $st_num $se_num in $fn");
2351 if (intval($se_seg_ct) != $st_segs_ct) {
2352 csv_edihist_log ("csv_x12_segments: ST-SE segment count mismatch $st_segs_ct $se_seg_ct in $fn");
2354 continue;
2356 if (substr($seg_text, 0, 4) == $iea_str) {
2357 $iea_seg = explode($elem_d, $seg_text);
2358 $iea_id = $iea_seg[2];
2359 $iea_ct++;
2361 if ($isa_id != $iea_id) {
2362 csv_edihist_log ("csv_x12_segments: ISA-IEA identifier mismatch set $iea_ct in $fn");
2364 if ($iea_ct == $isa_ct) {
2365 $trnset_seg_ct += $isa_segs_ct;
2366 if ($idx+1 != $trnset_seg_ct ) { //
2367 csv_edihist_log ("csv_x12_segments: IEA segment count error ({idx+1}:$trnset_seg_ct set) $iea_ct in $fn");
2369 } else {
2370 csv_edihist_log ("csv_x12_segments: ISA-IEA count mismatch set $isa_ct $iea_ct in $fn");
2376 return $ar_seg;