bug fix (#7559)
[openemr.git] / interface / main / finder / dynamic_finder_ajax.php
blob3308896e8b1cff647606f06f5d818aeea5251857
1 <?php
3 /**
4 * dynamic_finder_ajax.php
6 * Sponsored by David Eschelbacher, MD
8 * @package OpenEMR
9 * @link http://www.open-emr.org
10 * @author Rod Roark <rod@sunsetsystems.com>
11 * @author Brady Miller <brady.g.miller@gmail.com>
12 * @author Jerry Padgett <sjpadgett@gmail.com>
13 * @copyright Copyright (c) 2012 Rod Roark <rod@sunsetsystems.com>
14 * @copyright Copyright (c) 2018 Brady Miller <brady.g.miller@gmail.com>
15 * @copyright Copyright (c) 2019 Jerry Padgett <sjpadgett@gmail.com>
16 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
19 require_once(dirname(__FILE__) . "/../../globals.php");
20 require_once($GLOBALS['srcdir'] . "/options.inc.php");
22 use OpenEMR\Events\BoundFilter;
23 use OpenEMR\Events\PatientFinder\PatientFinderFilterEvent;
24 use OpenEMR\Events\PatientFinder\ColumnFilter;
26 // Not checking csrf since it breaks when opening up a patient in a new frame.
27 // Also note that csrf checking is not needed in this script because of following 2 reasons.
28 // 1. cookie_samesite in OpenEMR is set to 'Strict', which is an effective security measure to stop csrf vulnerabilities.
29 // 2. Additionally, in this script there are no state changes, thus it is not even sensitive to csrf vulnerabilities.
31 $popup = empty($_REQUEST['popup']) ? 0 : 1;
32 $searchAny = !empty($_GET['search_any']) && empty($_GET['sSearch']) ? $_GET['search_any'] : "";
34 // With the ColReorder or ColReorderWithResize plug-in, the expected column
35 // ordering may have been changed by the user. So we cannot depend on
36 // list_options to provide that.
37 // Addition of an any column search from dem layouts. sjp 05/04/2019
38 // Probably could have used a session var here because datatable server url
39 // presists not allowing easy way to unset any for normal search but opted not.
41 if ($searchAny) {
42 $_GET['sSearch'] = $searchAny;
43 $layoutCols = sqlStatement(
44 "SELECT field_id FROM layout_options WHERE form_id = 'DEM'
45 AND field_id not like ? AND field_id not like ? AND uor !=0",
46 array('em\_%', 'add%')
48 for ($iter = 0; $row = sqlFetchArray($layoutCols); $iter++) {
49 $aColumns[] = $row['field_id'];
51 } else {
52 $aColumns = explode(',', $_GET['sColumns']);
54 // Paging parameters. -1 means not applicable.
56 $iDisplayStart = isset($_GET['iDisplayStart' ]) ? 0 + $_GET['iDisplayStart' ] : -1;
57 $iDisplayLength = isset($_GET['iDisplayLength']) ? 0 + $_GET['iDisplayLength'] : -1;
58 $limit = '';
59 if ($iDisplayStart >= 0 && $iDisplayLength >= 0) {
60 $limit = "LIMIT " . escape_limit($iDisplayStart) . ", " . escape_limit($iDisplayLength);
62 // Search parameter. -1 means .
64 $searchMethodInPatientList = isset($_GET['searchType' ]) && $_GET['searchType' ] === "true" ? true : false;
66 // Column sorting parameters.
68 $orderby = '';
69 if (isset($_GET['iSortCol_0'])) {
70 for ($i = 0; $i < intval($_GET['iSortingCols']); ++$i) {
71 $iSortCol = intval($_GET["iSortCol_$i"]);
72 if ($_GET["bSortable_$iSortCol"] == "true") {
73 $sSortDir = escape_sort_order($_GET["sSortDir_$i"]); // ASC or DESC
74 // We are to sort on column # $iSortCol in direction $sSortDir.
75 $orderby .= $orderby ? ', ' : 'ORDER BY ';
77 if ($aColumns[$iSortCol] == 'name') {
78 $orderby .= "lname $sSortDir, fname $sSortDir, mname $sSortDir";
79 } else {
80 $orderby .= "`" . escape_sql_column_name($aColumns[$iSortCol], array('patient_data')) . "` $sSortDir";
86 // Helper function for filtering dates. Returns a string for use with MySQL LIKE.
87 // Examples (assuming US date formats):
88 // 12 => Any date with "12" in it
89 // 1977 => Any date with "1977" in it (therefore year 1977)
90 // 197/12/1 => Dec. 1 of any year in the 1970's
91 // 12/1/197 => Same
92 // 12/1 => Dec. 1 of any year
93 // /1 => The first day of any month of any year
94 // Any non-digit character may be used instead of "/".
96 function dateSearch($sSearch)
98 // Determine if MDY date format is used, preferring Date Display Format from
99 // global settings if it's not YMD, otherwise guessing from country code.
100 $mdy = empty($GLOBALS['date_display_format']) ?
101 ($GLOBALS['phone_country_code'] == 1) : ($GLOBALS['date_display_format'] == 1);
102 // If no delimiters then just search the whole date.
103 $mystr = "%$sSearch%";
104 if (preg_match('/[^0-9]/', $sSearch)) {
105 // Delimiter found. Separate it all into year, month and day components.
106 $parts = preg_split('/[^0-9]/', $sSearch);
107 $parts[1] = $parts[1] ?? '';
108 $parts[2] = $parts[2] ?? '';
109 // If the first part is more than 2 digits then assume y/m/d format.
110 // Otherwise assume MDY or DMY format as appropriate.
111 if (strlen($parts[0]) <= 2) {
112 $parts = $mdy ? array($parts[2], $parts[0], $parts[1]) :
113 array($parts[2], $parts[1], $parts[0]);
115 // A single-digit day or month is zero-filled. Fill in other missing
116 // digits with wildcards. A 2-digit year like 19 becomes 19__, not __19.
117 $parts[0] = substr($parts[0] . '____', 0, 4);
118 if (strlen($parts[1]) == 0) {
119 $parts[1] = '__';
120 } elseif (strlen($parts[1]) == 1) {
121 $parts[1] = '0' . $parts[1];
123 if (strlen($parts[2]) == 0) {
124 $parts[2] = '__';
125 } elseif (strlen($parts[2]) == 1) {
126 $parts[2] = '0' . $parts[2];
128 $mystr = $parts[0] . '-' . $parts[1] . '-' . $parts[2];
130 return $mystr;
133 // Global filtering.
135 $where = "";
136 $srch_bind = array();
137 if (isset($_GET['sSearch']) && $_GET['sSearch'] !== "") {
138 $sSearch = trim($_GET['sSearch']);
139 foreach ($aColumns as $colname) {
140 $where .= $where ? " OR " : " ( ";
141 if ($colname == 'name') {
142 $where .=
143 "lname LIKE ? OR " .
144 "fname LIKE ? OR " .
145 "mname LIKE ? ";
146 if ($searchMethodInPatientList) { // exact search
147 array_push($srch_bind, $sSearch, $sSearch, $sSearch);
148 } else {// like search
149 array_push($srch_bind, ($sSearch . "%"), ($sSearch . "%"), ($sSearch . "%"));
151 } elseif ($searchMethodInPatientList) { // exact search
152 $where .= "`" . escape_sql_column_name($colname, array('patient_data')) . "` LIKE ? ";
153 array_push($srch_bind, $sSearch);
154 } elseif ($searchAny) {
155 $where .= " `" . escape_sql_column_name($colname, array('patient_data')) . "` LIKE ?"; // any search
156 array_push($srch_bind, ('%' . $sSearch . '%'));
157 } else {
158 $where .= "`" . escape_sql_column_name($colname, array('patient_data')) . "` LIKE ? ";
159 array_push($srch_bind, ($sSearch . '%'));
163 if ($where) {
164 $where .= ")";
168 // Column-specific filtering.
170 $columnFilters = [];
171 for ($i = 0; $i < count($aColumns); ++$i) {
172 $colname = $aColumns[$i];
173 if (isset($_GET["bSearchable_$i"]) && $_GET["bSearchable_$i"] == "true" && $_GET["sSearch_$i"] != '') {
174 $where .= $where ? ' AND ' : '';
175 $sSearch = $_GET["sSearch_$i"];
176 $columnFilters[] = new ColumnFilter($colname, $sSearch);
177 if ($colname == 'name') {
178 $where .=
179 "lname LIKE ? OR " .
180 "fname LIKE ? OR " .
181 "mname LIKE ? ";
182 if ($searchMethodInPatientList) { // exact search
183 array_push($srch_bind, $sSearch, $sSearch, $sSearch);
184 } else {// like search
185 array_push($srch_bind, ($sSearch . "%"), ($sSearch . "%"), ($sSearch . "%"));
187 } elseif ($colname == 'DOB') {
188 $where .= "`" . escape_sql_column_name($colname, array('patient_data')) . "` LIKE ? ";
189 array_push($srch_bind, dateSearch($sSearch));
190 } elseif ($searchMethodInPatientList) { // exact search
191 $where .= "`" . escape_sql_column_name($colname, array('patient_data')) . "` LIKE ? ";
192 array_push($srch_bind, $sSearch);
193 } else {
194 $where .= "`" . escape_sql_column_name($colname, array('patient_data')) . "` LIKE ? ";
195 array_push($srch_bind, ($sSearch . '%'));
200 // Custom filtering, before datatables filtering created by the user
201 // This allows a module to subscribe to a 'patient-finder.filter' event and
202 // add filtering before data ever gets to the user
203 $patientFinderFilterEvent = new PatientFinderFilterEvent(new BoundFilter(), $aColumns, $columnFilters);
204 $patientFinderFilterEvent = $GLOBALS["kernel"]->getEventDispatcher()->dispatch($patientFinderFilterEvent, PatientFinderFilterEvent::EVENT_HANDLE, 10);
205 $boundFilter = $patientFinderFilterEvent->getBoundFilter();
206 $customWhere = $boundFilter->getFilterClause();
207 $srch_bind = array_merge($boundFilter->getBoundValues(), $srch_bind);
209 // Compute list of column names for SELECT clause.
210 // Always includes pid because we need it for row identification.
212 if ($searchAny) {
213 $aColumns = explode(',', $_GET['sColumns']);
215 $sellist = 'pid';
216 foreach ($aColumns as $colname) {
217 if ($colname == 'pid') {
218 continue;
221 $sellist .= ", ";
222 if ($colname == 'name') {
223 $sellist .= "lname, fname, mname";
224 } else {
225 $sellist .= "`" . escape_sql_column_name($colname, array('patient_data')) . "`";
229 // Get total number of rows in the table.
230 // Include the custom filter clause and bound values, if any
231 $row = sqlQuery("SELECT COUNT(id) AS count FROM patient_data WHERE $customWhere", $boundFilter->getBoundValues());
232 $iTotal = $row['count'];
234 // Get total number of rows in the table after filtering.
236 if (empty($where)) {
237 $where = $customWhere;
238 } else {
239 $where = "$customWhere AND ( $where )";
241 $row = sqlQuery("SELECT COUNT(id) AS count FROM patient_data WHERE $where", $srch_bind);
242 $iFilteredTotal = $row['count'];
244 // Build the output data array.
246 $out = array(
247 "sEcho" => intval($_GET['sEcho']),
248 "iTotalRecords" => $iTotal,
249 "iTotalDisplayRecords" => $iFilteredTotal,
250 "aaData" => array()
253 // save into variable data about fields of 'patient_data' from 'layout_options'
254 $fieldsInfo = array();
255 $quoteSellist = preg_replace('/(\w+)/i', '"${1}"', str_replace('`', '', $sellist));
256 $res = sqlStatement('SELECT data_type, field_id, list_id FROM layout_options WHERE form_id = "DEM" AND field_id IN(' . $quoteSellist . ')');
257 while ($row = sqlFetchArray($res)) {
258 $fieldsInfo[$row['field_id']] = $row;
261 $query = "SELECT $sellist FROM patient_data WHERE $where $orderby $limit";
262 $res = sqlStatement($query, $srch_bind);
263 while ($row = sqlFetchArray($res)) {
264 // Each <tr> will have an ID identifying the patient.
265 $arow = array('DT_RowId' => 'pid_' . $row['pid']);
266 foreach ($aColumns as $colname) {
267 if ($colname == 'name') {
268 $name = $row['lname'];
269 if ($name && $row['fname']) {
270 $name .= ', ';
273 if ($row['fname']) {
274 $name .= $row['fname'];
277 if ($row['mname']) {
278 $name .= ' ' . $row['mname'];
281 $arow[] = attr($name);
282 } else {
283 $arow[] = isset($fieldsInfo[$colname]) ? attr(generate_plaintext_field($fieldsInfo[$colname], $row[$colname])) : attr($row[$colname]);
287 $out['aaData'][] = $arow;
290 // error_log($query); // debugging
292 // Dump the output array as JSON.
294 // Encoding with options for escaping a special chars - JSON_HEX_TAG (<)(>), JSON_HEX_AMP(&), JSON_HEX_APOS('), JSON_HEX_QUOT(").
295 echo json_encode($out, 15);