Integrating the problem list improvements with the recent fee sheet improvements...
[openemr.git] / custom / code_types.inc.php
blobf647b6753a6be8a4721edbccf7dcc6d1794d517f
1 <?php
2 /**
3 * Library and data structure to manage Code Types and code type lookups.
5 * The data structure is the $code_types array.
6 * The $code_types array is built from the code_types sql table and provides
7 * abstraction of diagnosis/billing code types. This is desirable
8 * because different countries or fields of practice use different methods for
9 * coding diagnoses, procedures and supplies. Fees will not be relevant where
10 * medical care is socialized.
11 * <pre>Attributes of the $code_types array are:
12 * active - 1 if this code type is activated
13 * id - the numeric identifier of this code type in the codes table
14 * claim - 1 if this code type is used in claims
15 * fee - 1 if fees are used, else 0
16 * mod - the maximum length of a modifier, 0 if modifiers are not used
17 * just - the code type used for justification, empty if none
18 * rel - 1 if other billing codes may be "related" to this code type
19 * nofs - 1 if this code type should NOT appear in the Fee Sheet
20 * diag - 1 if this code type is for diagnosis
21 * proc - 1 if this code type is a procedure/service
22 * label - label used for code type
23 * external - 0 for storing codes in the code table
24 * 1 for storing codes in external ICD10 Diagnosis tables
25 * 2 for storing codes in external SNOMED (RF1) Diagnosis tables
26 * 3 for storing codes in external SNOMED (RF2) Diagnosis tables
27 * 4 for storing codes in external ICD9 Diagnosis tables
28 * 5 for storing codes in external ICD9 Procedure/Service tables
29 * 6 for storing codes in external ICD10 Procedure/Service tables
30 * 7 for storing codes in external SNOMED Clinical Term tables
31 * 8 for storing codes in external SNOMED (RF2) Clinical Term tables (for future)
32 * 9 for storing codes in external SNOMED (RF1) Procedure Term tables
33 * 10 for storing codes in external SNOMED (RF2) Procedure Term tables (for future)
34 * term - 1 if this code type is used as a clinical term
35 * problem - 1 if this code type is used as a medical problem
37 * </pre>
39 * Copyright (C) 2006-2010 Rod Roark <rod@sunsetsystems.com>
41 * LICENSE: This program is free software; you can redistribute it and/or
42 * modify it under the terms of the GNU General Public License
43 * as published by the Free Software Foundation; either version 2
44 * of the License, or (at your option) any later version.
45 * This program is distributed in the hope that it will be useful,
46 * but WITHOUT ANY WARRANTY; without even the implied warranty of
47 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48 * GNU General Public License for more details.
49 * You should have received a copy of the GNU General Public License
50 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
52 * @package OpenEMR
53 * @author Rod Roark <rod@sunsetsystems.com>
54 * @author Brady Miller <brady@sparmy.com>
55 * @author Kevin Yeh <kevin.y@integralemr.com>
56 * @link http://www.open-emr.org
59 require_once(dirname(__FILE__)."/../library/csv_like_join.php");
61 $code_types = array();
62 $default_search_type = '';
63 $ctres = sqlStatement("SELECT * FROM code_types WHERE ct_active=1 ORDER BY ct_seq, ct_key");
64 while ($ctrow = sqlFetchArray($ctres)) {
65 $code_types[$ctrow['ct_key']] = array(
66 'active' => $ctrow['ct_active' ],
67 'id' => $ctrow['ct_id' ],
68 'fee' => $ctrow['ct_fee' ],
69 'mod' => $ctrow['ct_mod' ],
70 'just' => $ctrow['ct_just'],
71 'rel' => $ctrow['ct_rel' ],
72 'nofs' => $ctrow['ct_nofs'],
73 'diag' => $ctrow['ct_diag'],
74 'mask' => $ctrow['ct_mask'],
75 'label'=> ( (empty($ctrow['ct_label'])) ? $ctrow['ct_key'] : $ctrow['ct_label'] ),
76 'external'=> $ctrow['ct_external'],
77 'claim' => $ctrow['ct_claim'],
78 'proc' => $ctrow['ct_proc'],
79 'term' => $ctrow['ct_term'],
80 'problem'=> $ctrow['ct_problem']
82 if ($default_search_type === '') $default_search_type = $ctrow['ct_key'];
85 /** This array contains metadata describing the arrangement of the external data
86 * tables for storing codes.
88 $code_external_tables=array();
89 define('EXT_COL_CODE','code');
90 define('EXT_COL_DESCRIPTION','description');
91 define('EXT_COL_DESCRIPTION_BRIEF','description_brief');
92 define('EXT_TABLE_NAME','table');
93 define('EXT_FILTER_CLAUSES','filter_clause');
94 define('EXT_VERSION_ORDER','filter_version_order');
95 define('EXT_JOINS','joins');
96 define('JOIN_TABLE','join');
97 define('JOIN_FIELDS','fields');
98 define('DISPLAY_DESCRIPTION',"display_description");
101 * This is a helper function for defining the metadata that describes the tables
103 * @param type $results A reference to the global array which stores all the metadata
104 * @param type $index The external table ID. This corresponds to the value in the code_types table in the ct_external column
105 * @param type $table_name The name of the table which stores the code informattion (e.g. icd9_dx_code
106 * @param type $col_code The name of the column which is the code
107 * @param type $col_description The name of the column which is the description
108 * @param type $col_description_brief The name of the column which is the brief description
109 * @param type $filter_clauses An array of clauses to be included in the search "WHERE" clause that limits results
110 * @param type $version_order How to choose between different revisions of codes
111 * @param type $joins An array which describes additional tables to join as part of a code search.
113 function define_external_table(&$results, $index, $table_name,$col_code, $col_description,$col_description_brief,$filter_clauses=array(),$version_order="",$joins=array(),$display_desc="")
115 $results[$index]=array(EXT_TABLE_NAME=>$table_name,
116 EXT_COL_CODE=>$col_code,
117 EXT_COL_DESCRIPTION=>$col_description,
118 EXT_COL_DESCRIPTION_BRIEF=>$col_description_brief,
119 EXT_FILTER_CLAUSES=>$filter_clauses,
120 EXT_JOINS=>$joins,
121 EXT_VERSION_ORDER=>$version_order,
122 DISPLAY_DESCRIPTION=>$display_desc
125 // In order to treat all the code types the same for lookup_code_descriptions, we include metadata for the original codes table
126 define_external_table($code_external_tables,0,'codes','code','code_text','code_text_short',array(),'id');
128 // ICD9 External Definitions
129 define_external_table($code_external_tables,4,'icd9_dx_code','formatted_dx_code','long_desc','short_desc',array("active='1'"),'revision DESC');
130 define_external_table($code_external_tables,5,'icd9_sg_code','formatted_sg_code','long_desc','short_desc',array("active='1'"),'revision DESC');
131 //**** End ICD9 External Definitions
133 // SNOMED Definitions
134 // For generic SNOMED-CT, there is no need to join with the descriptions table to get a specific description Type
136 // For generic concepts, use the fully specified description (DescriptionType=3) so we can tell the difference between them.
137 define_external_table($code_external_tables,7,'sct_descriptions','ConceptId','Term','Term',array("DescriptionStatus=0","DescriptionType=3"),"");
140 // To determine codes, we need to evaluate data in both the sct_descriptions table, and the sct_concepts table.
141 // the base join with sct_concepts is the same for all types of SNOMED definitions, so we define the common part here
142 $SNOMED_joins=array(JOIN_TABLE=>"sct_concepts",JOIN_FIELDS=>array("sct_descriptions.ConceptId=sct_concepts.ConceptId"));
144 // For disorders, use the preferred term (DescriptionType=1)
145 define_external_table($code_external_tables,2,'sct_descriptions','ConceptId','Term','Term',array("DescriptionStatus=0","DescriptionType=1"),"",array($SNOMED_joins));
146 // Add the filter to choose only disorders. This filter happens as part of the join with the sct_concepts table
147 array_push($code_external_tables[2][EXT_JOINS][0][JOIN_FIELDS],"FullySpecifiedName like '%(disorder)'");
149 // SNOMED-PR definition
150 define_external_table($code_external_tables,9,'sct_descriptions','ConceptId','Term','Term',array("DescriptionStatus=0","DescriptionType=1"),"",array($SNOMED_joins));
151 // Add the filter to choose only procedures. This filter happens as part of the join with the sct_concepts table
152 array_push($code_external_tables[9][EXT_JOINS][0][JOIN_FIELDS],"FullySpecifiedName like '%(procedure)'");
155 //**** End SNOMED Definitions
157 // ICD 10 Definitions
158 define_external_table($code_external_tables,1,'icd10_dx_order_code','formatted_dx_code','long_desc','short_desc',array("active='1'","valid_for_coding = '1'"),'revision DESC');
159 define_external_table($code_external_tables,6,'icd10_pcs_order_code','pcs_code','long_desc','short_desc',array("active='1'","valid_for_coding = '1'"),'revision DESC');
160 //**** End ICD 10 Definitions
163 * This array stores the external table options. See above for $code_types array
164 * 'external' attribute for explanation of the option listings.
165 * @var array
167 $cd_external_options = array(
168 '0' => xl('No'),
169 '4' => xl('ICD9 Diagnosis'),
170 '5' => xl('ICD9 Procedure/Service'),
171 '1' => xl('ICD10 Diagnosis'),
172 '6' => xl('ICD10 Procedure/Service'),
173 '2' => xl('SNOMED (RF1) Diagnosis'),
174 '7' => xl('SNOMED (RF1) Clinical Term'),
175 '9' => xl('SNOMED (RF1) Procedure')
179 * Checks is fee are applicable to any of the code types.
181 * @return boolean
183 function fees_are_used() {
184 global $code_types;
185 foreach ($code_types as $value) { if ($value['fee'] && $value['active']) return true; }
186 return false;
190 * Checks is modifiers are applicable to any of the code types.
191 * (If a code type is not set to show in the fee sheet, then is ignored)
193 * @param boolean $fee_sheet Will ignore code types that are not shown in the fee sheet
194 * @return boolean
196 function modifiers_are_used($fee_sheet=false) {
197 global $code_types;
198 foreach ($code_types as $value) {
199 if ($fee_sheet && !empty($value['nofs'])) continue;
200 if ($value['mod'] && $value['active']) return true;
202 return false;
206 * Checks if justifiers are applicable to any of the code types.
208 * @return boolean
210 function justifiers_are_used() {
211 global $code_types;
212 foreach ($code_types as $value) { if (!empty($value['just']) && $value['active']) return true; }
213 return false;
217 * Checks is related codes are applicable to any of the code types.
219 * @return boolean
221 function related_codes_are_used() {
222 global $code_types;
223 foreach ($code_types as $value) { if ($value['rel'] && $value['active']) return true; }
224 return false;
228 * Convert a code type id (ct_id) to the key string (ct_key)
230 * @param integer $id
231 * @return string
233 function convert_type_id_to_key($id) {
234 global $code_types;
235 foreach ($code_types as $key => $value) {
236 if ($value['id'] == $id) return $key;
241 * Checks if a key string (ct_key) is selected for an element/filter(s)
243 * @param string $key
244 * @param array $filter (array of elements that can include 'active','fee','rel','nofs','diag','claim','proc','term','problem')
245 * @return boolean
247 function check_code_set_filters($key,$filters=array()) {
248 global $code_types;
250 if (empty($filters)) return false;
252 foreach ($filters as $filter) {
253 if ($code_types[$key][$filter] != 1) return false;
256 // Filter was passed
257 return true;
261 * Return listing of pertinent and active code types.
263 * Function will return listing (ct_key) of pertinent
264 * active code types, such as diagnosis codes or procedure
265 * codes in a chosen format. Supported returned formats include
266 * as 1) an array and as 2) a comma-separated lists that has been
267 * process by urlencode() in order to place into URL address safely.
269 * @param string $category category of code types('diagnosis', 'procedure', 'clinical_term', 'active' or 'medical_problem')
270 * @param string $return_format format or returned code types ('array' or 'csv')
271 * @return string/array
273 function collect_codetypes($category,$return_format="array") {
274 global $code_types;
276 $return = array();
278 foreach ($code_types as $ct_key => $ct_arr) {
279 if (!$ct_arr['active']) continue;
281 if ($category == "diagnosis") {
282 if ($ct_arr['diag']) {
283 array_push($return,$ct_key);
286 else if ($category == "procedure") {
287 if ($ct_arr['proc']) {
288 array_push($return,$ct_key);
291 else if ($category == "clinical_term") {
292 if ($ct_arr['term']) {
293 array_push($return,$ct_key);
296 else if ($category == "active") {
297 if ($ct_arr['active']) {
298 array_push($return,$ct_key);
301 else if ($category == "medical_problem") {
302 if ($ct_arr['problem']) {
303 array_push($return,$ct_key);
306 else {
307 //return nothing since no supported category was chosen
311 if ($return_format == "csv") {
312 //return it as a csv string
313 return csv_like_join($return);
315 else { //$return_format == "array"
316 //return the array
317 return $return;
322 * Return the code information for a specific code.
324 * Function is able to search a variety of code sets. See the code type items in the comments at top
325 * of this page for a listing of the code sets supported.
327 * @param string $form_code_type code set key
328 * @param string $code code
329 * @param boolean $active if true, then will only return active entries (not pertinent for PROD code sets)
330 * @return recordset will contain only one item (row).
332 function return_code_information($form_code_type,$code,$active=true) {
333 return code_set_search($form_code_type,$code,false,$active,true);
337 * The main code set searching function.
339 * It will work for searching one or numerous code sets simultaneously.
340 * Note that when searching numerous code sets, you CAN NOT search the PROD
341 * codes; the PROD codes can only be searched by itself.
343 * @param string/array $form_code_type code set key(s) (can either be one key in a string or multiple/one key(s) in an array
344 * @param string $search_term search term
345 * @param integer $limit Number of results to return (NULL means return all)
346 * @param string $category Category of code sets. This WILL OVERRIDE the $form_code_type setting (category options can be found in the collect_codetypes() function above)
347 * @param boolean $active if true, then will only return active entries
348 * @param array $modes Holds the search modes to process along with the order of processing (if NULL, then default behavior is sequential code then description search)
349 * @param boolean $count if true, then will only return the number of entries
350 * @param integer $start Query start limit (for pagination) (Note this setting will override the above $limit parameter)
351 * @param integer $number Query number returned (for pagination) (Note this setting will override the above $limit parameter)
352 * @param array $filter_elements Array that contains elements to filter
353 * @return recordset/integer Will contain either a integer(if counting) or the results (recordset)
355 function main_code_set_search($form_code_type,$search_term,$limit=NULL,$category=NULL,$active=true,$modes=NULL,$count=false,$start=NULL,$number=NULL,$filter_elements=array()) {
357 // check for a category
358 if (!empty($category)) {
359 $form_code_type = collect_codetypes($category,"array");
362 // do the search
363 if (!empty($form_code_type)) {
364 if ( is_array($form_code_type) && (count($form_code_type) > 1) ) {
365 // run the multiple code set search
366 return multiple_code_set_search($form_code_type,$search_term,$limit,$modes,$count,$active,$start,$number,$filter_elements);
368 if ( is_array($form_code_type) && (count($form_code_type) == 1) ) {
369 // prepare the variable (ie. convert the one array item to a string) for the non-multiple code set search
370 $form_code_type = $form_code_type[0];
372 // run the non-multiple code set search
373 return sequential_code_set_search($form_code_type,$search_term,$limit,$modes,$count,$active,$start,$number,$filter_elements);
378 * Main "internal" code set searching function.
380 * Function is able to search a variety of code sets. See the 'external' items in the comments at top
381 * of this page for a listing of the code sets supported. Also note that Products (using PROD as code type)
382 * is also supported. (This function is not meant to be called directly)
384 * @param string $form_code_type code set key (special keywords are PROD) (Note --ALL-- has been deprecated and should be run through the multiple_code_set_search() function instead)
385 * @param string $search_term search term
386 * @param boolean $count if true, then will only return the number of entries
387 * @param boolean $active if true, then will only return active entries (not pertinent for PROD code sets)
388 * @param boolean $return_only_one if true, then will only return one perfect matching item
389 * @param integer $start Query start limit
390 * @param integer $number Query number returned
391 * @param array $filter_elements Array that contains elements to filter
392 * @param integer $limit Number of results to return (NULL means return all); note this is ignored if set $start/number
393 * @param array $mode 'default' mode searches code and description, 'code' mode only searches code, 'description' mode searches description (and separates words); note this is ignored if set $return_only_one to TRUE
394 * @param array $return_query This is a mode that will only return the query (everything except for the LIMIT is included) (returned as an array to include the query string and binding array)
395 * @return recordset/integer/array
397 function code_set_search($form_code_type,$search_term="",$count=false,$active=true,$return_only_one=false,$start=NULL,$number=NULL,$filter_elements=array(),$limit=NULL,$mode='default',$return_query=false) {
398 global $code_types,$code_external_tables;
400 // Figure out the appropriate limit clause
401 $limit_query = limit_query_string($limit,$start,$number,$return_only_one);
403 // build the filter_elements sql code
404 $query_filter_elements="";
405 if (!empty($filter_elements)) {
406 foreach ($filter_elements as $key => $element) {
407 $query_filter_elements .= " AND codes." . add_escape_custom($key) . "=" . "'" . add_escape_custom($element) . "' ";
411 if ($form_code_type == 'PROD') { // Search for products/drugs
412 if ($count) {
413 $query = "SELECT count(dt.drug_id) as count ";
415 else {
416 $query = "SELECT dt.drug_id, dt.selector, d.name ";
418 $query .= "FROM drug_templates AS dt, drugs AS d WHERE " .
419 "( d.name LIKE ? OR " .
420 "dt.selector LIKE ? ) " .
421 "AND d.drug_id = dt.drug_id " .
422 "ORDER BY d.name, dt.selector, dt.drug_id $limit_query";
423 $res = sqlStatement($query, array("%".$search_term."%", "%".$search_term."%") );
425 else { // Start a codes search
426 // We are looking up the external table id here. An "unset" value gets treated as 0(zero) without this test. This way we can differentiate between "unset" and explicitly zero.
427 $table_id=isset($code_types[$form_code_type]['external']) ? intval(($code_types[$form_code_type]['external'])) : -9999 ;
428 if($table_id>=0) // We found a definition for the given code search, so start building the query
430 // Place the common columns variable here since all check codes table
431 $common_columns=" codes.id, codes.code_type, codes.modifier, codes.units, codes.fee, " .
432 "codes.superbill, codes.related_code, codes.taxrates, codes.cyp_factor, " .
433 "codes.active, codes.reportable, codes.financial_reporting, ";
434 $columns .= $common_columns . "'" . add_escape_custom($form_code_type) . "' as code_type_name ";
436 $active_query = '';
437 if ($active) {
438 // Only filter for active codes. Only the active column in the joined table
439 // is affected by this parameter. Any filtering as a result of "active" status
440 // in the external table itself is always applied. I am implementing the behavior
441 // just as was done prior to the refactor
442 // - Kevin Yeh
443 // If there is no entry in codes sql table, then default to active
444 // (this is reason for including NULL below)
445 if ($table_id==0) {
446 // Search from default codes table
447 $active_query=" AND codes.active = 1 ";
449 else {
450 // Search from external tables
451 $active_query=" AND (codes.active = 1 || codes.active IS NULL) ";
455 // Get/set the basic metadata information
456 $table_info=$code_external_tables[$table_id];
457 $table=$table_info[EXT_TABLE_NAME];
458 $table_dot=$table.".";
459 $code_col=$table_info[EXT_COL_CODE];
460 $code_text_col=$table_info[EXT_COL_DESCRIPTION];
461 $code_text_short_col=$table_info[EXT_COL_DESCRIPTION_BRIEF];
462 if ($table_id==0) {
463 $table_info[EXT_FILTER_CLAUSES]=array("code_type=".$code_types[$form_code_type]['id']); // Add a filter for the code type
465 $code_external = $code_types[$form_code_type]['external'];
467 // If the description is supposed to come from "joined" table instead of the "main",
468 // the metadata defines a DISPLAY_DESCRIPTION element, and we use that to build up the query
469 if($table_info[DISPLAY_DESCRIPTION]!="")
471 $display_description=$table_info[DISPLAY_DESCRIPTION];
472 $display_description_brief=$table_info[DISPLAY_DESCRIPTION];
474 else
476 $display_description=$table_dot.$code_text_col;
477 $display_description_brief=$table_dot.$code_text_short_col;
479 // Ensure the external table exists
480 $check_table = sqlQuery("SHOW TABLES LIKE '".$table."'");
481 if ( (empty($check_table)) ) {HelpfulDie("Missing table in code set search:".$table);}
483 $sql_bind_array = array();
484 if ($count) {
485 // only collecting a count
486 $query = "SELECT count(".$table_dot.$code_col . ") as count ";
488 else {
489 $query = "SELECT '" . $code_external ."' as code_external, " .
490 $table_dot.$code_col . " as code, " .
491 $display_description . " as code_text, " .
492 $display_description_brief . " as code_text_short, " .
493 $columns . " ";
495 if ($table_id==0) {
496 // Search from default codes table
497 $query .= " FROM ".$table." ";
499 else {
500 // Search from external tables
501 $query .= " FROM ".$table.
502 " LEFT OUTER JOIN `codes` " .
503 " ON ".$table_dot.$code_col." = codes.code AND codes.code_type = ? ";
504 array_push($sql_bind_array,$code_types[$form_code_type]['id']);
507 foreach($table_info[EXT_JOINS] as $join_info)
509 $join_table=$join_info[JOIN_TABLE];
510 $check_table = sqlQuery("SHOW TABLES LIKE '".$join_table."'");
511 if ( (empty($check_table)) ) {HelpfulDie("Missing join table in code set search:".$join_table);}
512 $query.=" INNER JOIN ". $join_table;
513 $query.=" ON ";
514 $not_first=false;
515 foreach($join_info[JOIN_FIELDS] as $field)
517 if($not_first)
519 $query.=" AND ";
521 $query.=$field;
522 $not_first=true;
526 // Setup the where clause based on MODE
527 $query.= " WHERE ";
528 if ($return_only_one) {
529 $query .= $table_dot.$code_col." = ? ";
530 array_push($sql_bind_array,$search_term);
532 else if($mode=="code") {
533 $query.= $table_dot.$code_col." like ? ";
534 array_push($sql_bind_array,$search_term."%");
536 else if($mode=="description"){
537 $description_keywords=preg_split("/ /",$search_term,-1,PREG_SPLIT_NO_EMPTY);
538 $query.="(1=1 ";
539 foreach($description_keywords as $keyword)
541 $query.= " AND ".$table_dot.$code_text_col." LIKE ? ";
542 array_push($sql_bind_array,"%".$keyword."%");
544 $query.=")";
546 else { // $mode == "default"
547 $query .= "(".$table_dot.$code_text_col. " LIKE ? OR ".$table_dot.$code_col. " LIKE ?) ";
548 array_push($sql_bind_array,"%".$search_term."%","%".$search_term."%");
550 // Done setting up the where clause by mode
552 // Add the metadata related filter clauses
553 foreach($table_info[EXT_FILTER_CLAUSES] as $filter_clause)
555 $query.=" AND ";
556 $dot_location=strpos($filter_clause,".");
557 if($dot_location!==false) {
558 // The filter clause already includes a table specifier, so don't add one
559 $query .=$filter_clause;
561 else {
562 $query .=$table_dot.$filter_clause;
566 $query .=$active_query . $query_filter_elements;
568 $query .= " ORDER BY ".$table_dot.$code_col."+0,".$table_dot.$code_col;
570 if ($return_query) {
571 // Just returning the actual query without the LIMIT information in it. This
572 // information can then be used to combine queries of different code types
573 // via the mysql UNION command. Returning an array to contain the query string
574 // and the binding parameters.
575 return array('query'=>$query,'binds'=>$sql_bind_array);
578 $query .= $limit_query;
580 $res = sqlStatement($query,$sql_bind_array);
582 else
584 HelpfulDie("Code type not active or not defined:".$join_info[JOIN_TABLE]);
586 } // End specific code type search
588 if (isset($res)) {
589 if ($count) {
590 // just return the count
591 $ret = sqlFetchArray($res);
592 return $ret['count'];
594 else {
595 // return the data
596 return $res;
602 * Lookup Code Descriptions for one or more billing codes.
604 * Function is able to lookup code descriptions from a variety of code sets. See the 'external'
605 * items in the comments at top of this page for a listing of the code sets supported.
607 * @param string $codes Is of the form "type:code;type:code; etc.".
608 * @param string $desc_detail Can choose either the normal description('code_text') or the brief description('code_text_short').
609 * @return string Is of the form "description;description; etc.".
611 function lookup_code_descriptions($codes,$desc_detail="code_text") {
612 global $code_types, $code_external_tables;
614 // ensure $desc_detail is set properly
615 if ( ($desc_detail != "code_text") && ($desc_detail != "code_text_short") ) {
616 $desc_detail="code_text";
619 $code_text = '';
620 if (!empty($codes)) {
621 $relcodes = explode(';', $codes);
622 foreach ($relcodes as $codestring) {
623 if ($codestring === '') continue;
624 list($codetype, $code) = explode(':', $codestring);
625 $table_id=$code_types[$codetype]['external'];
626 if(isset($code_external_tables[$table_id]))
628 $table_info=$code_external_tables[$table_id];
629 $table_name=$table_info[EXT_TABLE_NAME];
630 $code_col=$table_info[EXT_COL_CODE];
631 $desc_col= $table_info[DISPLAY_DESCRIPTION]=="" ? $table_info[EXT_COL_DESCRIPTION] : $table_info[DISPLAY_DESCRIPTION];
632 $desc_col_short= $table_info[DISPLAY_DESCRIPTION]=="" ? $table_info[EXT_COL_DESCRIPTION_BRIEF] : $table_info[DISPLAY_DESCRIPTION];
633 $sqlArray = array();
634 $sql = "SELECT ".$desc_col." as code_text,".$desc_col_short." as code_text_short FROM ".$table_name;
636 // include the "JOINS" so that we get the preferred term instead of the FullySpecifiedName when appropriate.
637 foreach($table_info[EXT_JOINS] as $join_info)
639 $join_table=$join_info[JOIN_TABLE];
640 $check_table = sqlQuery("SHOW TABLES LIKE '".$join_table."'");
641 if ( (empty($check_table)) ) {HelpfulDie("Missing join table in code set search:".$join_table);}
642 $sql.=" INNER JOIN ". $join_table;
643 $sql.=" ON ";
644 $not_first=false;
645 foreach($join_info[JOIN_FIELDS] as $field)
647 if($not_first)
649 $sql.=" AND ";
651 $sql.=$field;
652 $not_first=true;
656 $sql.=" WHERE ";
659 // Start building up the WHERE clause
661 // When using the external codes table, we have to filter by the code_type. (All the other tables only contain one type)
662 if ($table_id==0) { $sql .= " code_type = '".add_escape_custom($code_types[$codetype]['id'])."' AND "; }
664 // Specify the code in the query.
665 $sql .= $table_name.".".$code_col."=? ";
666 array_push($sqlArray,$code);
668 // We need to include the filter clauses
669 // For SNOMED and SNOMED-CT this ensures that we get the Preferred Term or the Fully Specified Term as appropriate
670 // It also prevents returning "inactive" results
671 foreach($table_info[EXT_FILTER_CLAUSES] as $filter_clause)
673 $sql.= " AND ".$filter_clause;
675 // END building the WHERE CLAUSE
678 if($table_info[EXT_VERSION_ORDER]){$sql .= " ORDER BY ".$table_info[EXT_VERSION_ORDER];}
680 $sql .= " LIMIT 1";
681 $crow = sqlQuery($sql,$sqlArray);
682 if (!empty($crow[$desc_detail])) {
683 if ($code_text) $code_text .= '; ';
684 $code_text .= $crow[$desc_detail];
688 else {
689 //using an external code that is not yet supported, so skip.
693 return $code_text;
697 * Sequential code set "internal" searching function
699 * Function is basically a wrapper of the code_set_search() function to support
700 * a optimized searching models. The default mode will:
701 * Searches codes first; then if no hits, it will then search the descriptions
702 * (which are separated by each word in the code_set_search() function).
703 * (This function is not meant to be called directly)
705 * @param string $form_code_type code set key (special keyword is PROD) (Note --ALL-- has been deprecated and should be run through the multiple_code_set_search() function instead)
706 * @param string $search_term search term
707 * @param integer $limit Number of results to return (NULL means return all)
708 * @param array $modes Holds the search modes to process along with the order of processing (default behavior is described in above function comment)
709 * @param boolean $count if true, then will only return the number of entries
710 * @param boolean $active if true, then will only return active entries
711 * @param integer $start Query start limit (for pagination)
712 * @param integer $number Query number returned (for pagination)
713 * @param array $filter_elements Array that contains elements to filter
714 * @param string $is_hit_mode This is a mode that simply returns the name of the mode if results were found
715 * @return recordset/integer/string
717 function sequential_code_set_search($form_code_type,$search_term,$limit=NULL,$modes=NULL,$count=false,$active=true,$start=NULL,$number=NULL,$filter_elements=array(),$is_hit_mode=false) {
718 // Set the default behavior that is described in above function comments
719 if (empty($modes)) {
720 $modes=array('code','description');
723 // Return the Search Results (loop through each mode in order)
724 foreach ($modes as $mode) {
725 $res = code_set_search($form_code_type,$search_term,$count,$active,false,$start,$number,$filter_elements,$limit,$mode);
726 if ( ($count && $res>0) || (!$count && sqlNumRows($res)>0) ) {
727 if ($is_hit_mode) {
728 // just return the mode
729 return $mode;
731 else {
732 // returns the count number if count is true or returns the data if count is false
733 return $res;
740 * Code set searching "internal" function for when searching multiple code sets.
742 * It will also work for one code set search, although not meant for this.
743 * (This function is not meant to be called directly)
745 * @param array $form_code_types code set keys (will default to checking all active code types if blank)
746 * @param string $search_term search term
747 * @param integer $limit Number of results to return (NULL means return all)
748 * @param array $modes Holds the search modes to process along with the order of processing (default behavior is described in above function comment)
749 * @param boolean $count if true, then will only return the number of entries
750 * @param boolean $active if true, then will only return active entries
751 * @param integer $start Query start limit (for pagination)
752 * @param integer $number Query number returned (for pagination)
753 * @param array $filter_elements Array that contains elements to filter
754 * @return recordset/integer
756 function multiple_code_set_search($form_code_types=array(),$search_term,$limit=NULL,$modes=NULL,$count=false,$active=true,$start=NULL,$number=NULL,$filter_elements=array()) {
758 if (empty($form_code_types)) {
759 // Collect the active code types
760 $form_code_types = collect_codetypes("active","array");
763 if ($count) {
764 //start the counter
765 $counter = 0;
767 else {
768 // Figure out the appropriate limit clause
769 $limit_query = limit_query_string($limit,$start,$number);
771 // Prepare the sql bind array
772 $sql_bind_array = array();
774 // Start the query string
775 $query = "SELECT * FROM ((";
778 // Loop through each code type
779 $flag_first = true;
780 $flag_hit = false; //ensure there is a hit to avoid trying an empty query
781 foreach ($form_code_types as $form_code_type) {
782 // see if there is a hit
783 $mode_hit = NULL;
784 // only use the count method here, since it's much more efficient than doing the actual query
785 $mode_hit = sequential_code_set_search($form_code_type,$search_term,NULL,$modes,true,$active,NULL,NULL,$filter_elements,true);
786 if ($mode_hit) {
787 if ($count) {
788 // count the hits
789 $count_hits = code_set_search($form_code_type,$search_term,$count,$active,false,NULL,NULL,$filter_elements,NULL,$mode_hit);
790 // increment the counter
791 $counter += $count_hits;
793 else {
794 $flag_hit = true;
795 // build the query
796 $return_query = code_set_search($form_code_type,$search_term,$count,$active,false,NULL,NULL,$filter_elements,NULL,$mode_hit,true);
797 if (!empty($sql_bind_array)) {
798 $sql_bind_array = array_merge($sql_bind_array,$return_query['binds']);
800 else {
801 $sql_bind_array = $return_query['binds'];
803 if (!$flag_first) {
804 $query .= ") UNION ALL (";
806 $query .= $return_query['query'];
808 $flag_first = false;
812 if ($count) {
813 //return the count
814 return $counter;
816 else {
817 // Finish the query string
818 $query .= ")) as atari $limit_query";
820 // Process and return the query (if there was a hit)
821 if ($flag_hit) {
822 return sqlStatement($query,$sql_bind_array);
828 * Returns the limit to be used in the sql query for code set searches.
830 * @param integer $limit Number of results to return (NULL means return all)
831 * @param integer $start Query start limit (for pagination)
832 * @param integer $number Query number returned (for pagination)
833 * @param boolean $return_only_one if true, then will only return one perfect matching item
834 * @return recordset/integer
836 function limit_query_string($limit=NULL,$start=NULL,$number=NULL,$return_only_one=false) {
837 if ( !is_null($start) && !is_null($number) ) {
838 // For pagination of results
839 $limit_query = " LIMIT $start, $number ";
841 else if (!is_null($limit)) {
842 $limit_query = " LIMIT $limit ";
844 else {
845 // No pagination and no limit
846 $limit_query = '';
848 if ($return_only_one) {
849 // Only return one result (this is where only matching for exact code match)
850 // Note this overrides the above limit settings
851 $limit_query = " LIMIT 1 ";
853 return $limit_query;