* * * @link: https://www.open-emr.org * @author: Kevin McCormick * @package: OpenEMR * @subpackage: ediHistory */ /* ********* project notes ================= * determine GET and POST array elements * process new files -- type and csv data values * display tables -- links with GET and POST * find files -- find transactions * format display * * ========================================== */ /*********** php code here ****************************************************************/ /** * Class to read EDI X12 files in healthcare setting * * It is assumed that EDI X12 files will have mime-type text/plain; charset=us-ascii * * initialize with file path or as empty object, e.g. * $x12_file = new edih_x12_file(filepath); segment array and envelope array, no file text * $x12_file = new edih_x12_file(filepath, false); no segment or envelope array, no file text * $x12_file = new edih_x12_file(filepath, false, true); no segment or envelope array, yes file text * or * $x12_file = new edih_x12_file(); empty object, ' _x12_ ' methods available if file text supplied as method argument * * The properties filename, type, version, valid, isx12, hasGS, hasST, and delimiters should be available * if the valid filepath is provided when creating the object. * * @param string $filepath default = '' * @param bool $mk_segs default = true * @param bool $text default = false * @return bool|string true for empty object "ovgis" for validated x12 */ class edih_x12_file { // properties private $filepath = ''; private $filename = ''; private $type = ''; private $version = ''; private $text = ''; private $length = 0; private $valid = false; private $isx12 = false; private $hasGS = false; private $hasST = false; private $message = array(); private $delimiters = array(); private $segments = array(); private $envelopes = array(); // private $constructing = false; // private $gstype_ar = array('HB' => '271', 'HS' => '270', 'HR' => '276', 'HN' => '277', 'HI' => '278', 'HP' => '835', 'FA' => '999', 'HC' => '837'); // function __construct($file_path = '', $mk_segs = true, $text = false) { // if ($file_path === '') { return true; } // if (is_file($file_path) && is_readable($file_path)) { $this->filepath = trim($file_path); $this->filename = basename($this->filepath); $f_text = file_get_contents($this->filepath); // $testval = ($f_text) ? $this->edih_x12_scan($f_text) : ''; $this->valid = ( strpos($testval, 'v') ) ? true : false; $this->isx12 = ( strpos($testval, 'i') ) ? true : false; $this->hasGS = ( strpos($testval, 'g') ) ? true : false; $this->hasST = ( strpos($testval, 's') ) ? true : false; // if ($this->valid) { $this->constructing = true; $this->text = ($text) ? $f_text : ''; $this->length = ($f_text) ? strlen($f_text) : 0; if ($this->isx12) { $this->delimiters = $this->edih_x12_delimiters(substr($f_text, 0, 126)); $this->version = substr($f_text, 84, 5); if ($mk_segs) { $this->segments = $this->edih_x12_segments($f_text); if (is_array($this->segments) && count($this->segments)) { $this->envelopes = $this->edih_x12_envelopes(); $this->type = $this->edih_x12_type(); } else { $this->message[] = 'edih_x12_file: error in creating segment array ' . text($this->filename) . PHP_EOL; } } else { // read file contents to try and determine x12 type $this->type = $this->edih_x12_type($f_text); } } } } else { // invalid file path $this->message[] = 'edih_x12_file: invalid file path ' . text($file_path); } $this->constructing = false; return $this->valid; } /* * function to support empty object and '_x12_' functions called with supplied file text * * @param string $file_text * @param bool return x12 type * @param bool return delimiters * @param bool return segments * @return array array['filetext'] and maybe ['type'] ['$delimiters'] ['segments'] */ private function edih_file_text($file_text, $type = false, $delimiters = false, $segments = false) { // $ret_ar = array(); if (!$file_text || is_string($file_text) == false) { $this->message[] = 'edih_file_text(): invalid argument'; return $ret_ar; } // do verifications $v = $this->edih_x12_scan($file_text); if (!strpos($v, 's')) { $this->message[] = 'edih_file_text(): failed scan of file text (' . text($v) . ')'; return $ret_ar; } // $this->constructing = true; // if ($type) { $ret_ar['type'] = $this->edih_x12_type($file_text); } if ($delimiters) { $ret_ar['delimiters'] = $this->edih_x12_delimiters(substr($file_text, 0, 126)); } if ($segments) { $ret_ar['segments'] = $this->edih_x12_segments($file_text); } // $this->constructing = false; // return $ret_ar; } /* * functions to return properties */ public function classname() { return get_class($this); } public function edih_filepath() { return $this->filepath; } public function edih_filename() { return $this->filename; } public function edih_type() { return $this->type; } public function edih_version() { return $this->version; } public function edih_text() { return $this->text; } public function edih_length() { return $this->length; } public function edih_valid() { return $this->valid; } public function edih_isx12() { return $this->isx12; } public function edih_hasGS() { return $this->hasGS; } public function edih_hasST() { return $this->hasST; } public function edih_delimiters() { return $this->delimiters; } public function edih_segments() { return $this->segments; } public function edih_envelopes() { return $this->envelopes; } /** * message statements regarding object or from functions * formatted as html * * @return string */ public function edih_message() { $str_html = '
' . PHP_EOL;
if (count($this->message)) {
foreach ($this->message as $msg) {
$str_html .= text($msg) . '
' . PHP_EOL;
}
$str_html .= PHP_EOL . '
* array('t'=>segment terminator, 'e'=>element delimiter, * 's'=>sub-element delimiter, 'r'=>repetition delimiter) ** * @param string $isa_str110 first n>=106 characters of x12 file * @return array array or empty on error */ public function edih_x12_delimiters($isa_str110 = '') { // $delim_ar = array(); if (!$isa_str110 && $this->text) { $isa_str = substr($this->text, 0, 106); } else { $isa_str = trim($isa_str110); } $isalen = strlen($isa_str); if ($isalen >= 106) { if (substr($isa_str, 0, 3) != 'ISA') { // not the starting characters $this->message[] = 'edih_x12_delimiters: text does not begin with ISA'; return $delim_ar; } /* Extract delimiters using the prescribed positions. * -- problem is possibly mangled files * $t_ar['e'] = substr($isa_str, 3, 1); * $t_ar['r'] = substr($isa_str, 82, 1); * $t_ar['s'] = substr($isa_str, 104, 1); * $t_ar['t'] = substr($isa_str, 105, 1); */ } else { $this->message[] = 'edih_x12_delimiters: ISA string too short' . PHP_EOL; return $delim_ar; } $s = ''; $delim_ct = 0; $de = substr($isa_str, 3, 1); // ISA* for ($i = 0; $i < $isalen; $i++) { if ($isa_str[$i] == $de) { // element count incremented at end of loop // repetition separator in version 5010 if ($delim_ct == 11) { $dr = substr($s, 1, 1); } if ($delim_ct == 12) { if (strpos($s, '501') === false) { $dr = ''; } } // if ($delim_ct == 15) { $ds = substr($isa_str, $i + 1, 1); $dt = substr($isa_str, $i + 2, 1); } if ($delim_ct == 16) { break; } $s = $isa_str[$i]; // $elem_delim; $delim_ct++; } else { $s .= $isa_str[$i]; } } // there are 16 elements in ISA segment if ($delim_ct < 16) { // too few elements -- probably did not get delimiters $this->message[] = "edih_x12_delimiters: too few elements in ISA string"; return $delim_ar; } // $delim_ar = array('t' => $dt, 'e' => $de, 's' => $ds, 'r' => $dr); // return $delim_ar; } /** * Create a multidimensional array of edi envelope info from object segments. * Useful for slicing and dicing. The ['ST'][$stky]['trace'] value is used only for 835 * or 999 type files and the ['ST'][$stky]['acct'][i] array will have multiple values * likely only for 835, 271, and 277 types, because response from a payer will have * multiple transactions in the ST-SE envelope while OpenEMR probably will place each * transaction in its own ST-SE envelope for 270 and 837 types. * * The ['start'] and ['count'] values are for use in php function array_slice() * The numeric keys of the segments array begin at 1 and the ['start'] value is one less * than the actual key because array_slice() offset is zero-based. * *
* ['ISA'][$icn]=>['start']['count']['sender']['receiver']['icn']['gscount']['date'] * ['GS'][$gs_ct]=>['start']['count']['gsn']['icn']['sender']['date']['stcount']['type'] * ['ST'][$stky]=>['start']['count']['stn']['gsn']['icn']['type']['trace']['acct'] * ['ST'][$stky]['acct'][i]=>CLM01 * ['ST'][$stky]['bht03'][i]=>BHT03 ** * @return array array as shown above or empty on error */ public function edih_x12_envelopes($file_text = '') { // produce an array of envelopes and positions $env_ar = array(); $de = ''; if ($file_text) { // presume need for file scan and delimiters $vars = $this->edih_file_text($file_text, false, true, true); $segment_ar = (isset($vars['segments']) ) ? $vars['segments'] : array(); $de = (isset($vars['delimiters']) ) ? $vars['delimiters']['e'] : ''; //$segment_ar = $this->edih_x12_segments($file_text); if (empty($segment_ar) || !$de) { $this->message[] = 'edih_x12_envelopes: invalid file text'; return $env_ar; } } elseif (count($this->segments)) { $segment_ar = $this->segments; if (isset($this->delimiters['e'])) { $de = $this->delimiters['e']; } else { $de = (substr(reset($segment_ar), 0, 3) == 'ISA') ? substr(reset($segment_ar), 3, 1) : ''; } } else { $this->message[] = 'edih_x12_envelopes: no text or segments'; return $env_ar; } if (!$de) { $this->message[] = 'edih_x12_envelopes: invalid delimiters'; return $env_ar; } // // get the segment array bounds $seg_first = (reset($segment_ar) !== false) ? key($segment_ar) : '1'; $seg_last = (end($segment_ar) !== false) ? key($segment_ar) : count($segment_ar) + $seg_first; if (reset($segment_ar) === false) { $this->message[] = 'edi_x12_envelopes: reset() error in segment array'; return $env_ar; } else { $seg_ct = $seg_last + 1; } // variables $seg_txt = ''; $sn = ''; $st_type = ''; $st_ct = 0; $isa_ct = 0; $iea_ct = 0; $gs_st_ct = 0; $trnset_seg_ct = 0; $st_segs_ct = 0; $isa_segs_ct = 0; $chk_trn = false; $trncd = '2'; //$id278 = false; $ta1_icn = ''; $seg_ar = array(); // the segment IDs we look for $chk_segs = array('ISA', 'GS' . $de, 'TA1', 'ST' . $de, 'BHT', 'HL' . $de, 'TRN', 'CLP', 'CLM', 'SE' . $de, 'GE' . $de, 'IEA'); // for ($i = $seg_first; $i < $seg_ct; $i++) { // counters $isa_segs_ct++; $st_segs_ct++; // $seg_text = $segment_ar[$i]; $sn = substr($seg_text, 0, 4); // skip over segments that are not envelope boundaries or identifiers if (!in_array(substr($sn, 0, 3), $chk_segs)) { continue; } // create the structure array if (strncmp($sn, 'ISA' . $de, 4) == 0) { $seg_ar = explode($de, $seg_text); $icn = trim($seg_ar[13]); // $env_ar['ISA'][$icn]['start'] = strval($i - 1); $env_ar['ISA'][$icn]['sender'] = trim($seg_ar[6]); $env_ar['ISA'][$icn]['receiver'] = trim($seg_ar[8]); $env_ar['ISA'][$icn]['icn'] = $icn; $env_ar['ISA'][$icn]['date'] = trim($seg_ar[9]); // YYMMDD $env_ar['ISA'][$icn]['version'] = trim($seg_ar[12]); // $isa_segs_ct = 1; $isa_ct++; continue; } // if (strncmp($sn, 'GS' . $de, 3) == 0) { $seg_ar = explode($de, $seg_text); $gs_start = strval($i - 1); $gsn = $seg_ar[6]; // GS06 could be used to id 997/999 response, if truly unique // cannot index on $gsn due to concatenated ISA envelopes and non-unique $gs_ct = isset($env_ar['GS']) ? count($env_ar['GS']) : 0; // $env_ar['GS'][$gs_ct]['start'] = $gs_start; $env_ar['GS'][$gs_ct]['gsn'] = $gsn; $env_ar['GS'][$gs_ct]['icn'] = $icn; $env_ar['GS'][$gs_ct]['sender'] = trim($seg_ar[2]); $env_ar['GS'][$gs_ct]['date'] = trim($seg_ar[4]); $env_ar['GS'][$gs_ct]['srcid'] = ''; // to verify type of edi transaction if (array_key_exists($seg_ar[1], $this->gstype_ar)) { $gs_fid = $this->gstype_ar[$seg_ar[1]]; $env_ar['GS'][$gs_ct]['type'] = $seg_ar[1]; } else { $gs_fid = 'NA'; $env_ar['GS'][$gs_ct]['type'] = 'NA'; $this->message[] = 'edih_x12_envelopes: Unknown GS type ' . text($seg_ar[1]); } continue; } // expect 999 TA1 before ST if (strncmp($sn, 'TA1' . $de, 4) == 0) { $seg_ar = explode($de, $seg_text); if (isset($seg_ar[1]) && $seg_ar[1]) { $ta1_icn = $seg_ar[1]; } else { $this->message[] = 'edih_x12_envelopes: Error in TA1 segment response ICN'; } //TA1*ISA13ICN*ISA09DATE*ISA10TIME*ACKCode*NoteCode~ continue; } // if (strncmp($sn, 'ST' . $de, 3) == 0) { $seg_ar = explode($de, $seg_text); $stn = $seg_ar[2]; $st_type = $seg_ar[1]; $st_start = strval($i); $st_segs_ct = 1; $st_ct = isset($env_ar['ST']) ? count($env_ar['ST']) : 0; // $env_ar['ST'][$st_ct]['start'] = strval($i - 1); $env_ar['ST'][$st_ct]['count'] = ''; $env_ar['ST'][$st_ct]['stn'] = $seg_ar[2]; $env_ar['ST'][$st_ct]['gsn'] = $gsn; $env_ar['ST'][$st_ct]['icn'] = $icn; $env_ar['ST'][$st_ct]['type'] = $seg_ar[1]; $env_ar['ST'][$st_ct]['trace'] = '0'; $env_ar['ST'][$st_ct]['acct'] = array(); $env_ar['ST'][$st_ct]['bht03'] = array(); // GS file id FA can be 999 or 997 if ($gs_fid != $st_type && strpos($st_type, '99') === false) { $this->message[] = "edih_x12_envelopes: ISA " . text($icn) . ", GS " . text($gsn . " " . $gs_fid) . " ST " . text($stn . " " . $st_type) . " type mismatch" . PHP_EOL; } // continue; } // if (strpos('|270|271|276|277|278', $st_type)) { // if (strncmp($sn, 'BHT' . $de, 4) == 0) { $seg_ar = explode($de, $seg_text); if (isset($seg_ar[2])) { $trncd = ($seg_ar[2] == '13') ? '1' : '2'; // 13 = request, otherwise assume response } else { $this->message[] = 'edih_x12_envelopes: missing BHT02 type element'; } if (isset($seg_ar[3]) && $seg_ar[3]) { $env_ar['ST'][$st_ct]['bht03'][] = $seg_ar[3]; } else { $this->message[] = 'edih_x12_envelopes: missing BHT03 identifier'; } } if (strncmp($sn, 'HL' . $de, 3) == 0) { $seg_ar = explode($de, $seg_text); if (isset($seg_ar[3]) && $seg_ar[3]) { $chk_trn = ( strpos('|22|23|PT', $seg_ar[3]) ) ? true : false; } else { $this->message[] = 'edih_x12_envelopes: missing HL03 level element'; } continue; } if ($chk_trn && strncmp($sn, 'TRN' . $de, 4) == 0) { $seg_ar = explode($de, $seg_text); if (isset($seg_ar[1]) && $seg_ar[1] == $trncd) { $env_ar['ST'][$st_ct]['acct'][] = (isset($seg_ar[2])) ? $seg_ar[2] : ''; $chk_trn = false; } else { $this->message[] = 'edih_x12_envelopes: missing TRN02 type identifier element'; } continue; } } // if ($st_type == '835') { if (strncmp($sn, 'TRN' . $de, 4) == 0) { $seg_ar = explode($de, $seg_text); if (!isset($seg_ar[2]) || !isset($seg_ar[3])) { $this->message[] = 'error in 835 TRN segment ' . text($seg_text); } $env_ar['ST'][$st_ct]['trace'] = (isset($seg_ar[2])) ? $seg_ar[2] : ""; // to match OpenEMR billing parse file name if (isset($seg_ar[4])) { $env_ar['GS'][$gs_ct]['srcid'] = $seg_ar[4]; } else { $env_ar['GS'][$gs_ct]['srcid'] = (isset($seg_ar[3])) ? $seg_ar[3] : ""; } // continue; } if (strncmp($sn, 'CLP' . $de, 4) == 0) { $seg_ar = explode($de, $seg_text); if (isset($seg_ar[1])) { $env_ar['ST'][$st_ct]['acct'][] = $seg_ar[1]; } else { $this->message[] = 'error in 835 CLP segment ' . text($seg_text); } continue; } } // if ($st_type == '837') { if (strncmp($sn, 'BHT' . $de, 4) == 0) { $seg_ar = explode($de, $seg_text); if (isset($seg_ar[3]) && $seg_ar[3]) { $env_ar['ST'][$st_ct]['bht'][] = $seg_ar[3]; } else { $this->message[] = 'edih_x12_envelopes: missing BHT03 identifier'; } } // if (strncmp($sn, 'CLM' . $de, 4) == 0) { $seg_ar = explode($de, $seg_text); if (isset($seg_ar[1])) { $env_ar['ST'][$st_ct]['acct'][] = $seg_ar[1]; } else { $this->message[] = 'error in 837 CLM segment ' . text($seg_text); } continue; } } // if (strncmp($sn, 'SE' . $de, 3) == 0) { // make sure no lingering toggle $id278 = false; $chk_trn = false; // $seg_ar = explode($de, $seg_text); $se_num = $seg_ar[2]; $env_ar['ST'][$st_ct]['count'] = strval($seg_ar[1]); // 999 case: expect TA1 before ST, so capture batch icn here if ($st_type == '999' || $st_type == '997') { if (isset($ta1_icn) && strlen($ta1_icn)) { $env_ar['ST'][$st_ct]['trace'] = $ta1_icn; $ta1_icn = ''; } } // errors if ($se_num != $stn) { $this->message[] = 'edih_x12_envelopes: ST-SE number mismatch ' . text($stn) . ' ' . text($se_num) . ' in ISA ' . text($icn) . PHP_EOL; } if (intval($seg_ar[1]) != $st_segs_ct) { $this->message[] = 'edih_x12_envelopes: ST-SE segment count mismatch ' . text($st_segs_ct) . ' ' . text($seg_ar[1]) . ' in ISA ' . text($icn) . PHP_EOL; } continue; } // if (strncmp($sn, 'GE' . $de, 3) == 0) { $seg_ar = explode($de, $seg_text); $env_ar['GS'][$gs_ct]['count'] = $i - $gs_start - 1; $env_ar['GS'][$gs_ct]['stcount'] = trim($seg_ar[1]); // ST count $gs_st_ct += $seg_ar[1]; // if ($seg_ar[2] != $env_ar['GS'][$gs_ct]['gsn']) { $this->message[] = 'edih_x12_envelopes: GS-GE identifier mismatch' . PHP_EOL; } if ($gs_ct === 0 && ($seg_ar[1] != count($env_ar['ST']))) { $this->message[] = 'edih_x12_envelopes: GS count of ST mismatch' . PHP_EOL; } elseif ($gs_st_ct != count($env_ar['ST'])) { $this->message[] = 'edih_x12_envelopes: GS count of ST mismatch' . PHP_EOL; } continue; } // if (strncmp($sn, 'IEA' . $de, 4) == 0) { $seg_ar = explode($de, $seg_text); $env_ar['ISA'][$icn]['count'] = $isa_segs_ct; $env_ar['ISA'][$icn]['gscount'] = $seg_ar[1]; $iea_ct++; // if (count($env_ar['GS']) != $seg_ar[1]) { $this->message[] = 'edih_x12_envelopes: GS count mismatch in ISA ' . text($icn) . PHP_EOL; $gsct = count($env_ar['GS']); $this->message[] = 'GS group count: ' . text($gsct) . ' IEA01: ' . text($seg_ar[1]) . ' segment: ' . text($seg_text); } if ($env_ar['ISA'][$icn]['icn'] !== $seg_ar[2]) { $this->message[] = 'edih_x12_envelopes: ISA-IEA identifier mismatch ISA ' . text($icn) . ' IEA ' . text($seg_ar[2]); } if ($iea_ct == $isa_ct) { $trnset_seg_ct += $isa_segs_ct; //if ( $i+1 != $trnset_seg_ct ) { if ($i != $trnset_seg_ct) { $this->message[] = 'edih_x12_envelopes: IEA segment count error ' . text($i) . ' : ' . text($trnset_seg_ct); } } else { $this->message[] = 'edih_x12_envelopes: ISA-IEA count mismatch ISA ' . text($isa_ct) . ' IEA ' . text($iea_ct); } continue; } } // return $env_ar; } /** * Parse x12 file contents into array of segments. * * @uses edih_x12_delimiters() * @uses edih_x12_scan() * * @param string $file_text * @return array array['i'] = segment, or empty on error */ public function edih_x12_segments($file_text = '') { $ar_seg = array(); // do verifications if ($file_text) { if (!$this->constructing) { // need to validate file $vars = $this->edih_file_text($file_text, false, true, false); $f_str = $file_text; $dt = ( isset($vars['delimiters']['t']) ) ? $vars['delimiters']['t'] : ''; } else { $f_str = $file_text; if (isset($this->delimiters['t'])) { $dt = $this->delimiters['t']; } else { $delims = $this->edih_x12_delimiters(substr($f_str, 0, 126)); $dt = ( isset($delims['t']) ) ? $delims['t'] : ''; } } } elseif ($this->text) { $f_str = $this->text; if (isset($this->delimiters['t'])) { $dt = $this->delimiters['t']; } else { $delims = $this->edih_x12_delimiters(substr($f_str, 0, 126)); $dt = ( isset($delims['t']) ) ? $delims['t'] : ''; } } else { $this->message[] = 'edih_x12_segments: no file text'; return $ar_seg; } // did we get the segment terminator? if (!$dt) { $this->message[] = 'edih_x12_segments: invalid delimiters'; return $ar_seg; } // OK, now initialize variables $seg_pos = 0; // position where segment begins $seg_end = 0; $seg_ct = 0; $moresegs = true; // could test this against simple $segments = explode($dt, $f_str) while ($moresegs) { // extract each segment from the file text $seg_end = strpos($f_str, $dt, $seg_pos); $seg_text = substr($f_str, $seg_pos, $seg_end - $seg_pos); $seg_pos = $seg_end + 1; $moresegs = strpos($f_str, $dt, $seg_pos); $seg_ct++; // we trim in case there are line or carriage returns $ar_seg[$seg_ct] = trim($seg_text); } // return $ar_seg; } /** * extract the segments representing a transaction for CLM01 pt-encounter number * note: there may be more than one in a file, all matching are returned * 27x transactions will have unique BHT03 that could be used as the claimid argument * * return_array[i] => transaction segments array * return_array[i][j] => particular segment string * * @param string $clm01 837 CLM01 or BHT03 from 277 * @param string $stn ST number -- optional, limit search to that ST-SE envelope * @param string $filetext optional file contents * @return array multidimensional array of segments or empty on failure */ public function edih_x12_transaction($clm01, $stn = '', $filetext = '') { // $ret_ar = array(); // if (!$clm01) { $this->message[] = 'edih_x12_transaction: invalid argument'; return $ret_ar; } // $de = ''; $tp = ''; $seg_ar = array(); $env_ar = array(); // select the data to search if ($filetext && !$this->constructing) { $vars = $this->edih_file_text($filetext, true, true, true); $tp = ( isset($vars['type']) ) ? $vars['type'] : $tp; $de = ( isset($vars['delimiters']['e']) ) ? $vars['delimiters']['e'] : $de; $seg_ar = ( isset($vars['segments']) ) ? $vars['segments'] : $seg_ar; //$env_ar = $vars['envelopes']; // probably faster without envelopes in this case } elseif (count($this->segments)) { // default created object $seg_ar = $this->segments; if (count($this->delimiters)) { $de = $this->delimiters['e']; } else { $de = (substr(reset($segment_ar), 0, 3) == 'ISA') ? substr(reset($segment_ar), 3, 1) : ''; } $tp = ($this->type) ? $this->type : $this->edih_x12_type(); $env_ar = ( isset($this->envelopes['ST']) ) ? $this->envelopes : $env_ar; } elseif ($this->text) { // object with file text, but no processing $tp = $this->edih_x12_type(); $seg_ar = ( $tp ) ? $this->edih_x12_segments() : $seg_ar; if (count($seg_ar)) { $de = substr(reset($seg_ar), 3, 1); } } else { $this->message[] = 'edih_x12_transaction: invalid search data'; return $ret_ar; } if (!count($seg_ar)) { $this->message[] = 'edih_x12_transaction: invalid segments'; return $ret_ar; } if (!$de) { $this->message[] = 'edih_x12_transaction: invalid delimiters'; return $ret_ar; } //array('HB'=>'271', 'HS'=>'270', 'HR'=>'276', 'HI'=>'278', // 'HN'=>'277', 'HP'=>'835', 'FA'=>'999', 'HC'=>'837'); if (substr($tp, 0, 5) == 'mixed') { $tp = substr($tp, -2); } if (!strpos('|HB|271|HS|270|HR|276|HI|278|HN|277|HP|835|FA|999|HC|837', $tp)) { $this->message[] = 'edih_x12_transaction: wrong edi type for transaction search ' . text($tp); return $ret_ar; } $idx = 0; $is_found = false; $slice = array(); $srch_ar = array(); $sl_idx = 0; // there may be several in same ST envelope with the same $clm01, esp. 835 // we will get each set of relevant transaction segments in foreach() below if (count($env_ar)) { foreach ($env_ar['ST'] as $st) { if (strlen($stn) && $st['stn'] != $stn) { continue; } if (isset($st['acct']) && count($st['acct'])) { $ky = array_search($clm01, $st['acct']); if ($ky !== false) { $srch_ar[$idx]['array'] = array_slice($seg_ar, $st['start'], $st['count'], true); $srch_ar[$idx]['start'] = $st['start']; $srch_ar[$idx]['type'] = $st['type']; $idx++; } } } } // if not identified in envelope search, use segments if (!count($srch_ar)) { $srch_ar[0]['array'] = $seg_ar; $srch_ar[0]['start'] = 0; // with array_slice() the index is absolute zero base $srch_ar[0]['type'] = $tp; } // verify we have type if ($srch_ar[0]['type'] == 'NA' || !$srch_ar[0]['type']) { $this->edih_message('edih_x12_transaction(): invalid file type ' . text($srch_ar[0]['type'])); return $ret_ar; } // segments we check $test_id = array('TRN','CLM','CLP','ST' . $de,'BHT','REF','LX' . $de,'PLB','SE' . $de); // foreach ($srch_ar as $srch) { $idx = $srch['start'] - 1; // align index to segments array offset $type = (string)$srch['type']; $is_found = false; $idval = ''; $idlen = 1; // foreach ($srch['array'] as $key => $seg) { $idx++; // $test_str = substr($seg, 0, 3); if (!in_array($test_str, $test_id, true)) { continue; } // // the opening ST segment should be in each search array, // so type and search values can be determined here. if (strncmp($seg, 'ST' . $de, 3) == 0) { $stseg = explode($de, $seg); $type = ($type) ? $type : (string)$stseg[1]; // $idval = ( strpos('|HN|277|HB|271', $type) ) ? 'TRN' . $de . '2' . $de . $clm01 : ''; $idval = ( strpos('|HR|276|HS|270', $type) ) ? 'TRN' . $de . '1' . $de . $clm01 : $idval; $idval = ( strpos('|HI|278', $type) ) ? 'REF' . $de . 'EJ' . $de . $clm01 : $idval; $idval = ( strpos('|HC|837', $type) ) ? 'CLM' . $de . $clm01 . $de : $idval; $idval = ( strpos('|HP|835', $type) ) ? 'CLP' . $de . $clm01 . $de : $idval; $idlen = strlen($idval); // continue; } //array('HB'=>'271', 'HS'=>'270', 'HR'=>'276', 'HI'=>'278', // 'HN'=>'277', 'HP'=>'835', 'FA'=>'999', 'HC'=>'837'); // these types use the BHT segment to begin transactions if (strpos('|HI|278|HN|277|HR|276|HB|271|HS|270|HC|837', $type)) { // if (strncmp($seg, 'BHT' . $de, 4) === 0) { $bht_seg = explode($de, $seg); $bht_pos = $idx; //$bht_pos = $key; if ($is_found && isset($slice[$sl_idx]['start'])) { $slice[$sl_idx]['count'] = $idx - $slice[$sl_idx]['start']; //$slice[$sl_idx]['count'] = $key - $slice[$sl_idx]['start']; $is_found = false; $sl_idx++; } elseif (strcmp($clm01, $bht_seg[3]) === 0) { // matched by BHT03 identifier $is_found = true; $slice[$sl_idx]['start'] = $bht_pos; } continue; } // if (strncmp($seg, $idval, $idlen) === 0) { // matched by clm01 identifier (idval) $is_found = true; $slice[$sl_idx]['start'] = $bht_pos; continue; } } // if ($type == 'HP' || $type == '835') { if (strncmp($seg, 'CLP' . $de, 4) === 0) { if (strncmp($seg, $idval, $idlen) === 0) { if ($is_found && isset($slice[$sl_idx]['start'])) { $slice[$sl_idx]['count'] = $idx - $slice[$sl_idx]['start']; //$slice[$sl_idx]['count'] = $key - $slice[$sl_idx]['start']; $sl_idx++; } $is_found = true; $slice[$sl_idx]['start'] = $idx; //$slice[$sl_idx]['start'] = $key; } else { if ($is_found && isset($slice[$sl_idx]['start'])) { $slice[$sl_idx]['count'] = $idx - $slice[$sl_idx]['start']; //$slice[$sl_idx]['count'] = $key - $slice[$sl_idx]['start']; $is_found = false; $sl_idx++; } } continue; } // LX segment is often used to group claim payment information // we do not capture TS3 or TS2 segments in the transaction if (strncmp($seg, 'LX' . $de, 3) === 0) { if ($is_found && isset($slice[$sl_idx]['start'])) { $slice[$sl_idx]['count'] = $idx - $slice[$sl_idx]['start']; //$slice[$sl_idx]['count'] = $key - $slice[$sl_idx]['start']; $is_found = false; $sl_idx++; } continue; } // PLB segment is part of summary/trailer in 835 // not part of the preceeding transaction if (strncmp($seg, 'PLB' . $de, 4) === 0) { if ($is_found && isset($slice[$sl_idx]['start'])) { $slice[$sl_idx]['count'] = $idx - $slice[$sl_idx]['start']; //$slice[$sl_idx]['count'] = $key - $slice[$sl_idx]['start']; $is_found = false; $sl_idx++; } continue; } } // SE will always mark end of transaction segments if (strncmp($seg, 'SE' . $de, 3) === 0) { if ($is_found && isset($slice[$sl_idx]['start'])) { $slice[$sl_idx]['count'] = $idx - $slice[$sl_idx]['start']; //$slice[$sl_idx]['count'] = $key - $slice[$sl_idx]['start']; $is_found = false; $sl_idx++; } } } // end foreach($srch['array'] as $seg) } // end foreach($srch_ar as $srch) // if (count($slice)) { foreach ($slice as $sl) { $ret_ar[] = array_slice($seg_ar, $sl['start'], $sl['count'], true); } } // return $ret_ar; } /** * get the segment(s) with a particular ID, such as CLP, NM1, etc. * return is array * array[i] => matching segment string * * @param string $segmentID such as NM1, CLP, STC, etc. * @param string $srchStr optional string contained in segment * @param array $seg_array optional supplied array of segments to search * @return array */ public function edih_get_segment($segmentID, $srchStr = '', $seg_array = '') { // $ret_ar = array(); $seg_ar = array(); $segid = ( strlen($segmentID) ) ? trim($segmentID) : ''; // $srch = ( strlen($srchStr) ) ? $srchStr : ''; // if (!$segid) { $this->message[] = 'edih_get_segment(): missing segment ID'; return $ret_ar; } // $de = ( isset($this->delimiters['e']) ) ? $this->delimiters['e'] : ''; $dt = ( isset($this->delimiters['t']) ) ? $this->delimiters['t'] : ''; // // segment array from edih_x12_transaction() is two dimension if (is_array($seg_array) && count($seg_array)) { if (isset($seg_array[0]) && is_array($seg_array[0])) { foreach ($seg_array as $ar) { $seg_ar = array_merge($seg_ar, $ar); } } else { $seg_ar = $seg_array; } } elseif (count($this->segments)) { $seg_ar = $this->segments; } elseif ($this->text) { if (!$de) { $delims = $this->edih_x12_delimiters(substr($this->text, 0, 126)); $dt = ( isset($delims['t']) ) ? $delims['t'] : ''; $de = ( isset($delims['e']) ) ? $delims['e'] : ''; } if (!$de || !$dt) { $this->message[] = 'edih_get_segment() : unable to get delimiters'; return $ret_ar; } // $segsrch = ($segid == 'ISA') ? $segid . $de : $dt . $segid . $de; $seg_pos = 1; $see_pos = 2; while ($seg_pos) { $seg_pos = strpos($this->text, $segsrch, $seg_pos); $see_pos = strpos($this->text, $dt, $seg_pos + 1); if ($seg_pos) { $segstr = trim(substr($this->text, $seg_pos, $see_pos - $seg_pos), $dt); if ($srch) { if (strpos($segstr, $srch) !== false) { $ret_ar[] = $segstr; } } else { $ret_ar[] = $segstr; } $seg_pos = $see_pos + 1; } } } // if (count($seg_ar)) { $cmplen = strlen($segid . $de); foreach ($seg_ar as $key => $seg) { if (strncmp($seg, $segid . $de, $cmplen) === 0) { if ($srch) { if (strpos($seg, $srch) !== false) { $ret_ar[$key] = $seg; } } else { $ret_ar[$key] = $seg; } } } } else { $this->message[] = 'edih_get_segment() : no segments or text content available'; } // return $ret_ar; } /** * Get a slice of the segments array * Supply an array with one or more of the following keys and values: * * ['trace'] => trace value from 835(TRN02) or 999(TA101) x12 type * ['ISA13'] => ISA13 * ['GS06'] => GS06 (sconsider also 'ISA13') * ['ST02'] => ST02 (condider also 'ISA13' and 'GS06') * ['keys'] => true to preserve segment numbering from original file * * The return value will be an array of one or more segments. * The 'search' parameter results in one or more segments containing * the search string. The * @param array note: all element values except 'keys' are strings * @return array */ function edih_x12_slice($arg_array, $file_text = '') { // $ret_ar = array(); $f_str = ''; // see what we have if (!is_array($arg_array) || !count($arg_array)) { // debug $this->message[] = 'edih_x12_slice() invalid array argument'; return $ret_ar; } // if ($file_text) { // need to validate file edih_file_text($file_text, $type=false, $delimiters=false, $segments=false) $vars = $this->edih_file_text($file_text, true, true, false); if (is_array($vars) && count($vars)) { $f_str = $file_text; $dt = ( isset($vars['delimiters']['t']) ) ? $vars['delimiters']['t'] : ''; $de = ( isset($vars['delimiters']['e']) ) ? $vars['delimiters']['e'] : ''; $ft = ( isset($vars['type']) ) ? $vars['type'] : ''; //$seg_ar = ( isset($vars['segments']) ) ? $vars['segments'] : ''; //$env_ar = $this->edih_x12_envelopes($f_str); } else { $this->message[] = 'edih_x12_slice() error processing file text'; // debug //echo $this->edih_message().PHP_EOL; return $ret_ar; } } elseif (count($this->segments) && count($this->envelopes) && count($this->delimiters)) { $seg_ar = $this->segments; $env_ar = $this->envelopes; $dt = $this->delimiters['t']; $de = $this->delimiters['e']; $ft = $this->type; } else { $this->message[] = 'edih_x12_slice() object missing needed properties'; // debug //echo $this->edih_message().PHP_EOL; return $ret_ar; } // initialize search variables $trace = ''; $stn = ''; $gsn = ''; $icn = ''; $prskeys = false; // foreach ($arg_array as $key => $val) { switch ((string)$key) { case 'trace': $trace = (string)$val; break; case 'ST02': $stn = (string)$val; break; case 'GS06': $gsn = (string)$val; break; case 'ISA13': $icn = (string)$val; break; case 'keys': $prskeys = (bool)$val; break; } } // if ($trace && strpos('|HP|FA', $ft) === false) { $this->message[] = 'edih_x12_slice() incorrect type [' . text($ft) . '] for trace'; return $ret_ar; } // if ($f_str) { $srchstr = ''; if ($icn) { $icnpos = strpos($f_str, $de . $icn . $de); if ($icnpos === false) { // $icn not found $this->message[] = 'edih_x12_slice() did not find ISA13 ' . text($icn); // debug //echo $this->edih_message().PHP_EOL; return $ret_ar; } elseif ($icnpos < 106) { $isapos = 0; } else { $isapos = strrpos($f_str, $dt . 'ISA' . $de, ($icnpos - strlen($f_str))) + 1; } $ieapos = strpos($f_str, $de . $icn . $dt, $isapos); $ieapos = strpos($f_str, $dt, $ieapos) + 1; $segidx = ($prskeys) ? substr_count($f_str, $dt, 0, $isapos + 2) + 1 : 0; // $srchstr = substr($f_str, $isapos, $ieapos - $isapos); } if ($gsn) { $srchstr = ($srchstr) ? $srchstr : $f_str; $gspos = strpos($srchstr, $de . $gsn . $de); if ($gspos === false) { // $gsn not found $this->message[] = 'edih_x12_slice() did not find GS06 ' . text($gsn); return $ret_ar; } else { $gspos = strrpos(substr($srchstr, 0, $gspos), $dt) + 1; } $gepos = strpos($srchstr, $dt . 'GE' . $dt, $gspos); $gepos = strpos($srchstr, $dt, $gepos + 1) + 1; $segidx = ($prskeys) ? substr_count($f_str, $dt, 0, $gspos + 2) + 1 : 0; // $srchstr = substr($srchstr, $gspos, $gepos - $gspos); } if ($stn) { $srchstr = ($srchstr) ? $srchstr : $f_str; $sttp = $this->gstype_ar[$ft]; $seg_st = $dt . 'ST' . $de . $sttp . $de . $stn ; $seg_se = $dt . 'SE' . $de; // $segpos = 1; $stpos = strpos($srchstr, $seg_st); if ($stpos === false) { // $stn not found $this->message[] = 'edih_x12_slice() did not find ST02 ' . text($stn); return $ret_ar; } else { $stpos = $stpos + 1; } $sepos = strpos($srchstr, $seg_se, $stpos); $sepos = strpos($srchstr, $dt, $sepos + 1); $segidx = ($prskeys) ? substr_count($f_str, $dt, 0, $stpos + 2) + 1 : 0; // $srchstr = substr($srchstr, $stpos, $sepos - $stpos); } if ($trace) { // $trpos = strpos($f_str, $de . $trace); if ($trpos === false) { // $icn not found $this->message[] = 'edih_x12_slice() did not find trace ' . text($trace); return $ret_ar; } $sttp = $this->gstype_ar[$ft]; $seg_st = $dt . 'ST' . $de . $sttp . $de; $stpos = strrpos($f_str, $seg_st, ($trpos - strlen($f_str))); $sepos = strpos($f_str, $dt . 'SE' . $de, $stpos); $sepos = strpos($f_str, $dt, $sepos + 1); // $segidx = ($prskeys) ? substr_count($f_str, $dt, 0, $st_pos + 2) + 1 : 0; $srchstr = substr($f_str, $stpos + 1, $sepos - $stpos); } // if we have a match, the $srchstr should have the desired segments if ($trace || $icn || $gsn || $stn) { if ($srchstr) { $seg_ar = explode($dt, $srchstr); // to keep segment numbers same as original file foreach ($seg_ar as $seg) { $ret_ar[$segidx] = $seg; $segidx++; } return $ret_ar; } else { $this->message[] = 'edih_x12_slice() error creating substring'; return $ret_ar; } } // file_text not supplied, check for object values } elseif (!($seg_ar && $env_ar && $dt && $de && $ft)) { // debug $this->message[] = 'edih_x12_slice() error is processing file'; return $ret_ar; } // file_text not supplied, use object values if ($trace) { foreach ($env_ar['ST'] as $st) { if ($st['trace'] == $trace) { // have to add one to the count to capture the SE segment so html_str has data // when called from edih_835_payment_html function in edih_835_html.php 4-25-17 SMW $ret_ar = array_slice($seg_ar, $st['start'], $st['count'] + 1, $prskeys); break; } } } elseif ($icn && !($stn || $gsn)) { if (isset($env_ar['ISA'][$icn])) { $ret_ar = array_slice($seg_ar, $env_ar['ISA'][$icn]['start'], $env_ar['ISA'][$icn]['count'], $prskeys); } } elseif ($gsn && !$stn) { foreach ($env_ar['GS'] as $gs) { if ($icn) { if (($gs['icn'] == $icn) && ($gs['gsn'] == $gsn)) { $ret_ar = array_slice($seg_ar, $gs['start'], $gs['count'], $prskeys); break; } } else { if ($gs['gsn'] == $gsn) { $ret_ar = array_slice($seg_ar, $gs['start'], $gs['count'], $prskeys); break; } } } } elseif ($stn) { // ; foreach ($env_ar['ST'] as $st) { // if ($icn) { if ($gsn) { if ($st['icn'] == $icn && $st['gsn'] == $gsn && $st['stn'] == $stn) { $ret_ar = array_slice($seg_ar, $st['start'], $st['count'], $prskeys); break; } } else { if ($st['icn'] == $icn && $st['stn'] == $stn) { $ret_ar = array_slice($seg_ar, $st['start'], $st['count'], $prskeys); break; } } } elseif ($gsn) { if ($st['gsn'] == $gsn && $st['stn'] == $stn) { $ret_ar = array_slice($seg_ar, $st['start'], $st['count'], $prskeys); break; } } elseif ($st['stn'] == $stn) { // $ret_ar = array_slice($seg_ar, $st['start'], $st['count'], $prskeys); break; } } } else { $this->message[] = 'edih_x12_slice() no file text or invalid array argument keys or values'; } // if (!count($ret_ar)) { $this->message[] = 'edih_x12_slice() no match'; } return $ret_ar; } // end class edih_x12_file }