1<?php
2
3/**
4 * Observium
5 *
6 *   This file is part of Observium.
7 *
8 * @package    observium
9 * @subpackage db
10 * @copyright  (C) 2006-2013 Adam Armstrong, (C) 2013-2019 Observium Limited
11 *
12 */
13
14/* Here common DB functions which uses calls to specific api functions */
15
16// Initial variables
17$db_stats = array('insert'    => 0, 'insert_sec'    => 0,
18                  'update'    => 0, 'update_sec'    => 0,
19                  'delete'    => 0, 'delete_sec'    => 0,
20                  'fetchcell' => 0, 'fetchcell_sec' => 0,
21                  'fetchrow'  => 0, 'fetchrow_sec'  => 0,
22                  'fetchrows' => 0, 'fetchrows_sec' => 0,
23                  'fetchcol'  => 0, 'fetchcol_sec'  => 0);
24
25// Include DB api. Default and recommended mysqli, legacy mysql
26switch (OBS_DB_EXTENSION)
27{
28  case 'mysql':
29    if (@function_exists('mysql_connect'))
30    {
31      require($config['install_dir'] . '/includes/db/mysql.inc.php');
32    } else {
33      echo('ERROR. PHP mysql extension not exist. Execution is stopped.' . PHP_EOL);
34      exit(2);
35    }
36    break;
37  case 'mysqli':
38  default:
39    if (@function_exists('mysqli_connect'))
40    {
41      require($config['install_dir'] . '/includes/db/mysqli.inc.php');
42    } else {
43      echo('ERROR. PHP mysqli extension not exist. Execution is stopped.' . PHP_EOL);
44      exit(2);
45    }
46}
47
48/**
49 * Provides server status information
50 *
51 * @param string $scope GLOBAL or SESSION variable scope modifier
52 * @return array Array with server status variables
53 */
54function dbShowStatus($scope = 'SESSION')
55{
56  switch ($scope)
57  {
58    case 'GLOBAL':
59      $sql = 'SHOW GLOBAL STATUS;';
60      break;
61    default:
62      $sql = 'SHOW STATUS;';
63  }
64
65  $rows = array();
66  foreach (dbFetchRows($sql) as $row)
67  {
68    $rows[$row['Variable_name']] = $row['Value'];
69  }
70
71  return $rows;
72}
73
74/**
75 * Shows the values of MySQL system variables
76 *
77 * @param string $scope GLOBAL or SESSION variable scope modifier
78 * @param string $where WHERE or LIKE clause
79 * @return array Array with variables
80 */
81function dbShowVariables($scope = 'SESSION', $where = '')
82{
83  switch ($scope)
84  {
85    case 'GLOBAL':
86      $sql = 'SHOW GLOBAL VARIABLES';
87      break;
88    default:
89      $sql = 'SHOW VARIABLES';
90  }
91  if (strlen($where))
92  {
93    $sql .= ' ' . $where;
94  }
95
96  $rows = array();
97  foreach (dbFetchRows($sql) as $row)
98  {
99    $rows[$row['Variable_name']] = $row['Value'];
100  }
101
102  return $rows;
103}
104
105/**
106 * Provides table index list
107 *
108 * @param string $table Table name
109 * @param string $index_name Index name (if empty get all indexes)
110 * @return array Array with table indexes: array()->$key_name->$column_name
111 */
112function dbShowIndexes($table, $index_name = NULL)
113{
114  $table  = dbEscape($table);
115  $params = array();
116  if ($index_name)
117  {
118    $sql = 'SHOW INDEX FROM `'.$table.'` WHERE `Key_name` = ?;';
119    $params[] = $index_name;
120  } else {
121    $sql = 'SHOW INDEX FROM `'.$table.'`;';
122  }
123
124  $rows = array();
125  foreach (dbFetchRows($sql, $params) as $row)
126  {
127    $rows[$row['Key_name']][$row['Column_name']] = $row;
128  }
129
130  return $rows;
131}
132
133/*
134 * Performs a query using the given string.
135 * Used by the other _query functions.
136 * */
137function dbQuery($sql, $parameters = array(), $print_query = FALSE)
138{
139  global $fullSql;
140
141  $fullSql = dbMakeQuery($sql, $parameters);
142
143  if (OBS_DEBUG > 0 || $print_query)
144  {
145    // Pre query debug output
146    if (is_cli())
147    {
148      $debug_sql = explode(PHP_EOL, $fullSql);
149      print_message(PHP_EOL.'SQL[%y' . implode('%n'.PHP_EOL.'%y', $debug_sql) . '%n]', 'console', FALSE);
150    } else {
151      print_sql($fullSql);
152    }
153  }
154
155  if (OBS_DEBUG > 0 || $GLOBALS['config']['profile_sql'])
156  {
157    $time_start = microtime(true);
158  }
159
160  $result = dbCallQuery($fullSql); // sets $this->result
161
162  if (OBS_DEBUG > 0 || $GLOBALS['config']['profile_sql'])
163  {
164    $runtime = number_format(microtime(true) - $time_start, 8);
165    $debug_msg .= 'SQL RUNTIME['.($runtime > 0.05 ? '%r' : '%g').$runtime.'s%n]';
166    if ($GLOBALS['config']['profile_sql'])
167    {
168      #fwrite($this->logFile, date('Y-m-d H:i:s') . "\n" . $fullSql . "\n" . number_format($time_end - $time_start, 8) . " seconds\n\n");
169      $GLOBALS['sql_profile'][] = array('sql' => $fullSql, 'time' => $runtime);
170    }
171  }
172
173  if (OBS_DEBUG > 0)
174  {
175    if ($result === FALSE && (error_reporting() & 1))
176    {
177      $error_msg = 'Error in query: (' . dbError() . ') ' . dbErrorNo();
178      $debug_msg .= PHP_EOL . 'SQL ERROR[%r'.$error_msg.'%n]';
179    }
180    if ($warnings = dbWarnings())
181    {
182      $debug_msg .= PHP_EOL . "SQL WARNINGS[\n %m" . implode("%n\n %m", $warnings) . "%n\n]";
183    }
184
185    if (is_cli())
186    {
187      if (OBS_DEBUG > 1)
188      {
189        $rows = dbAffectedRows();
190        $debug_msg = 'ROWS['.($rows < 1 ? '%r' : '%g').$rows.'%n]'.PHP_EOL.$debug_msg;
191      }
192      // After query debug output for cli
193      print_message($debug_msg, 'console', FALSE);
194    } else {
195      print_error($error_msg);
196    }
197  }
198
199  if ($result === FALSE && isset($GLOBALS['config']['db']['debug']) && $GLOBALS['config']['db']['debug'])
200  {
201    logfile('db.log', 'Failed dbQuery (#' . dbErrorNo() . ' - ' . dbError() . '), Query: ' . $fullSql);
202  }
203
204  return $result;
205}
206
207/*
208 * This is intended to be the method used for large result sets.
209 * It is intended to return an iterator, and act upon buffered data.
210 * */
211function dbFetch($sql, $parameters = array(), $print_query = FALSE)
212{
213  return dbFetchRows($sql, $parameters, $print_query);
214}
215
216/*
217 * This method is quite different from fetchCell(), actually
218 * It fetches one cell from each row and places all the values in 1 array
219 * */
220function dbFetchColumn($sql, $parameters = array(), $print_query = FALSE)
221{
222  $time_start = microtime(true);
223  $cells = array();
224  foreach (dbFetchRows($sql, $parameters, $print_query) as $row)
225  {
226    $cells[] = array_shift($row);
227  }
228  $time_end = microtime(true);
229
230  $GLOBALS['db_stats']['fetchcol_sec'] += number_format($time_end - $time_start, 8);
231  $GLOBALS['db_stats']['fetchcol']++;
232
233  return $cells;
234}
235
236/*
237 * Should be passed a query that fetches two fields
238 * The first will become the array key
239 * The second the key's value
240 */
241function dbFetchKeyValue($sql, $parameters = array(), $print_query)
242{
243  $data = array();
244  foreach (dbFetchRows($sql, $parameters, $print_query) as $row)
245  {
246    $key = array_shift($row);
247    if (sizeof($row) == 1)
248    { // if there were only 2 fields in the result
249      // use the second for the value
250      $data[$key] = array_shift($row);
251    } else { // if more than 2 fields were fetched
252      // use the array of the rest as the value
253      $data[$key] = $row;
254    }
255  }
256
257  return $data;
258}
259
260/*
261 * Passed an array and a table name, it attempts to insert the data into the table.
262 * Check for boolean false to determine whether insert failed
263 * */
264function dbInsert($data, $table, $print_query = FALSE)
265{
266  global $fullSql;
267
268  // the following block swaps the parameters if they were given in the wrong order.
269  // it allows the method to work for those that would rather it (or expect it to)
270  // follow closer with SQL convention:
271  // insert into the TABLE this DATA
272  if (is_string($data) && is_array($table))
273  {
274    $tmp = $data;
275    $data = $table;
276    $table = $tmp;
277
278    print_debug('Parameters passed to dbInsert() were in reverse order.');
279  }
280
281  $sql = 'INSERT INTO `' . $table . '` (`' . implode('`,`', array_keys($data)) . '`)  VALUES (' . implode(',', dbPlaceHolders($data)) . ')';
282
283  $time_start = microtime(true);
284  //dbBeginTransaction();
285  $result = dbQuery($sql, $data, $print_query);
286  if ($result)
287  {
288    // This should return true if insert succeeded, but no ID was generated
289    $id = dbLastID();
290    //dbCommitTransaction();
291  } else {
292    //dbRollbackTransaction();
293    $id = FALSE;
294  }
295
296  $time_end = microtime(true);
297  $GLOBALS['db_stats']['insert_sec'] += number_format($time_end - $time_start, 8);
298  $GLOBALS['db_stats']['insert']++;
299
300  return $id;
301}
302
303/**
304 * Passed an array and a table name, it attempts to insert the data into the table.
305 * Check for boolean false to determine whether insert failed
306 */
307function dbInsertMulti($data, $table, $columns = NULL, $print_query = FALSE)
308{
309  global $fullSql;
310
311  // the following block swaps the parameters if they were given in the wrong order.
312  // it allows the method to work for those that would rather it (or expect it to)
313  // follow closer with SQL convention:
314  // insert into the TABLE this DATA
315  if (is_string($data) && is_array($table))
316  {
317    $tmp = $data;
318    $data = $table;
319    $table = $tmp;
320
321    print_debug('Parameters passed to dbInsertMulti() were in reverse order.');
322  }
323
324  // Detect if data is multiarray
325  $first_data = reset($data);
326  if (!is_array($first_data))
327  {
328    $first_data = $data;
329    $data = array($data);
330  }
331
332  // Columns, if not passed use keys from first element
333  if (empty($columns))
334  {
335    $columns = array_keys($first_data);
336  }
337
338  $values = array();
339  // Multiarray data
340  foreach ($data as $entry)
341  {
342    $entry = dbPrepareData($entry); // Escape data
343
344    // Keep same columns order as in first entry
345    $entries = array();
346    foreach ($columns as $column)
347    {
348      $entries[$column] = $entry[$column];
349    }
350
351    $values[] = '(' . implode(',', $entries) . ')';
352  }
353
354  $sql = 'INSERT INTO `' . $table . '` (`' . implode('`,`', $columns) . '`)  VALUES ' . implode(',', $values);
355
356  $time_start = microtime(true);
357  //dbBeginTransaction();
358  $result = dbQuery($sql, NULL, $print_query);
359  if ($result)
360  {
361    // This should return true if insert succeeded, but no ID was generated
362    $id = dbLastID();
363    //dbCommitTransaction();
364  } else {
365    //dbRollbackTransaction();
366    $id = FALSE;
367  }
368
369  $time_end = microtime(true);
370  $GLOBALS['db_stats']['insert_sec'] += number_format($time_end - $time_start, 8);
371  $GLOBALS['db_stats']['insert']++;
372
373  return $id;
374}
375
376/*
377 * Passed an array, table name, WHERE clause, and placeholder parameters, it attempts to update a record.
378 * Returns the number of affected rows
379 * */
380function dbUpdate($data, $table, $where = NULL, $parameters = array(), $print_query = FALSE)
381{
382  global $fullSql;
383
384  // the following block swaps the parameters if they were given in the wrong order.
385  // it allows the method to work for those that would rather it (or expect it to)
386  // follow closer with SQL convention:
387  // update the TABLE with this DATA
388  if (is_string($data) && is_array($table))
389  {
390    $tmp = $data;
391    $data = $table;
392    $table = $tmp;
393    //trigger_error('QDB - The first two parameters passed to update() were in reverse order, but it has been allowed', E_USER_NOTICE);
394  }
395
396  // need field name and placeholder value
397  // but how merge these field placeholders with actual $parameters array for the WHERE clause
398  $sql = 'UPDATE `' . $table . '` set ';
399  foreach ($data as $key => $value)
400  {
401    $sql .= "`".$key."` ". '=:' . $key . ',';
402  }
403  $sql = substr($sql, 0, -1); // strip off last comma
404
405  if ($where)
406  {
407    // Remove WHERE clause at the beginning and ; at end
408    $where = preg_replace(array('/^\s*WHERE\s+/i', '/\s*;\s*$/'), '', $where);
409    $sql .= ' WHERE ' . $where;
410    $data = array_merge($data, $parameters);
411  }
412
413  $time_start = microtime(true);
414  if (dbQuery($sql, $data, $print_query))
415  {
416    $return = dbAffectedRows();
417  } else {
418    $return = FALSE;
419  }
420  $time_end = microtime(true);
421  $GLOBALS['db_stats']['update_sec'] += number_format($time_end - $time_start, 8);
422  $GLOBALS['db_stats']['update']++;
423
424  return $return;
425}
426
427/**
428 * Passed an array and a table name, it attempts to update the data in the table.
429 * Check for boolean false to determine whether update failed
430 * This is really INSERT with ODKU update
431 * For key really better use only ID field!
432 * https://stackoverflow.com/questions/25674737/mysql-update-multiple-rows-with-different-values-in-one-query/25674827
433 */
434function dbUpdateMulti($data, $table, $columns = NULL, $print_query = FALSE)
435{
436  global $fullSql;
437
438  // the following block swaps the parameters if they were given in the wrong order.
439  // it allows the method to work for those that would rather it (or expect it to)
440  // follow closer with SQL convention:
441  // insert into the TABLE this DATA
442  if (is_string($data) && is_array($table))
443  {
444    $tmp = $data;
445    $data = $table;
446    $table = $tmp;
447
448    print_debug('Parameters passed to dbUpdateMulti() were in reverse order.');
449  }
450
451  // Detect if data is multiarray
452  $first_data = reset($data);
453  if (!is_array($first_data))
454  {
455    $first_data = $data;
456    $data = array($data);
457  }
458
459  // Columns, if not passed use keys from first element
460  $all_columns = array_keys($first_data); // All columns data and UNIQUE indexes
461  if (!empty($columns))
462  {
463    // Update only passed columns from param
464    $update_columns = $columns;
465  } else {
466    // Fallbak for all columns (also indexes),
467    // this is normal, UNIQUE indexes not updated anyway
468    $update_columns = $all_columns;
469  }
470
471  // Columns which will updated
472  foreach ($update_columns as $key)
473  {
474    $update_keys[] = '`'.$key.'`=VALUES(`'.$key.'`)';
475  }
476
477  $values = array();
478  // Multiarray data
479  foreach ($data as $entry)
480  {
481    $entry = dbPrepareData($entry); // Escape data
482
483    // Keep same columns order as in first entry
484    $entries = array();
485    foreach ($all_columns as $column)
486    {
487      $entries[$column] = $entry[$column];
488    }
489
490    $values[] = '(' . implode(',', $entries) . ')';
491  }
492
493  $sql = 'INSERT INTO `' . $table . '` (`' . implode('`,`', $all_columns) . '`)  VALUES ' . implode(',', $values);
494
495  // This is only way for update multiple rows at once
496  $sql .= ' ON DUPLICATE KEY UPDATE ' . implode(',', $update_keys);
497
498  $time_start = microtime(true);
499  //dbBeginTransaction();
500  if (dbQuery($sql, NULL, $print_query))
501  {
502    $return = dbAffectedRows(); // This value should be divided into two for innodb
503  } else {
504    $return = FALSE;
505  }
506
507  $time_end = microtime(true);
508  $GLOBALS['db_stats']['update_sec'] += number_format($time_end - $time_start, 8);
509  $GLOBALS['db_stats']['update']++;
510
511  return $return;
512}
513
514function dbExist($table, $where = NULL, $parameters = array(), $print_query = FALSE)
515{
516  $sql = 'SELECT EXISTS (SELECT 1 FROM `' . $table . '`';
517  if ($where)
518  {
519    // Remove WHERE clause at the beginning and ; at end
520    $where = preg_replace(array('/^\s*WHERE\s+/i', '/\s*;\s*$/'), '', $where);
521    $sql .= ' WHERE ' . $where;
522  }
523  $sql .= ')';
524
525  $return = dbFetchCell($sql, $parameters, $print_query);
526  //print_vars($return);
527
528  return (bool)$return;
529}
530
531function dbDelete($table, $where = NULL, $parameters = array(), $print_query = FALSE)
532{
533  $sql = 'DELETE FROM `' . $table.'`';
534  if ($where)
535  {
536    // Remove WHERE clause at the beginning and ; at end
537    $where = preg_replace(array('/^\s*WHERE\s+/i', '/\s*;\s*$/'), '', $where);
538    $sql .= ' WHERE ' . $where;
539  }
540
541  $time_start = microtime(true);
542  if (dbQuery($sql, $parameters, $print_query))
543  {
544    $return = dbAffectedRows();
545  } else {
546    $return = FALSE;
547  }
548  $time_end = microtime(true);
549  $GLOBALS['db_stats']['delete_sec'] += number_format($time_end - $time_start, 8);
550  $GLOBALS['db_stats']['delete']++;
551
552  return $return;
553}
554
555/*
556 * This combines a query and parameter array into a final query string for execution
557 * PDO drivers don't need to use this
558 */
559function dbMakeQuery($sql, $parameters)
560{
561  // bypass extra logic if we have no parameters
562
563  if (sizeof($parameters) == 0)
564  {
565    return $sql;
566  }
567
568  $parameters = dbPrepareData($parameters);
569  // separate the two types of parameters for easier handling
570  $questionParams = array();
571  $namedParams = array();
572  foreach ($parameters as $key => $value)
573  {
574    if (is_numeric($key))
575    {
576      $questionParams[] = $value;
577    } else {
578      $namedParams[':' . $key] = $value;
579    }
580  }
581
582  if (count($namedParams) == 0)
583  {
584    // use simple pattern if named params not used (this broke some queries)
585    $pattern = '/(\?)/';
586  } else {
587    // sort namedParams in reverse to stop substring squashing
588    krsort($namedParams);
589    // full pattern
590    $pattern = '/(\?|:[a-zA-Z0-9_-]+)/';
591  }
592
593  // split on question-mark and named placeholders
594  $result = preg_split($pattern, $sql, -1, PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE);
595
596  // every-other item in $result will be the placeholder that was found
597
598  $query = '';
599  for ($i = 0; $i < sizeof($result); $i+=2)
600  {
601    $query .= $result[$i];
602
603    $j = $i+1;
604    if (array_key_exists($j, $result))
605    {
606      $test = $result[$j];
607      if ($test == '?')
608      {
609        $query .= array_shift($questionParams);
610      } else {
611        $query .= $namedParams[$test];
612      }
613    }
614  }
615
616  return $query;
617}
618
619function dbPrepareData($data)
620{
621  $values = array();
622
623  foreach ($data as $key=>$value)
624  {
625    $escape = true;
626    // don't quote or esc if value is an array, we treat it
627    // as a "decorator" that tells us not to escape the
628    // value contained in the array IF there is one item in the array
629    if (is_array($value) && !is_object($value))
630    {
631      if (count($value) == 1)
632      {
633        $escape = false;
634        $value = array_shift($value);
635      } else {
636        // if this is a multi-value array, implode this as it's probably
637        // (hopefully) used in an IN statement.
638        $escape = false; // we'll escape on our own, thanks.
639        // escape each entry by itself, unfortunately requires an extra array
640        // but implode() can't first escape each string, of course.
641        foreach ($value as $entry)
642        {
643          $escaped[] = "'" . dbEscape($entry) . "'";
644        }
645        $value = implode(",", $escaped);
646      }
647    }
648
649    // it's not right to worry about invalid fields in this method because we may be operating on fields
650    // that are aliases, or part of other tables through joins
651    //if (!in_array($key, $columns)) // skip invalid fields
652    //  continue;
653    if ($escape)
654    {
655      $values[$key] = "'" . dbEscape($value) . "'";
656    } else {
657      $values[$key] = $value;
658    }
659  }
660
661  return $values;
662}
663
664/*
665 * Given a data array, this returns an array of placeholders
666 * These may be question marks, or ":email" type
667 */
668function dbPlaceHolders($values)
669{
670  $data = array();
671  foreach ($values as $key => $value)
672  {
673    if (is_numeric($key))
674    {
675      $data[] = '?';
676    } else {
677      $data[] = ':' . $key;
678    }
679  }
680  return $data;
681}
682
683/**
684 * This function generates WHERE condition string from array with values
685 * NOTE, value should be exploded by comma before use generate_query_values(), for example in get_vars()
686 *
687 * @param mixed  $value       Values
688 * @param string $column      Table column name
689 * @param string $condition   Compare condition, known: =, !=, NOT, NULL, NOT NULL, LIKE (and variants %LIKE%, %LIKE, LIKE%)
690 * @param bool   $leading_and Add leading AND to result query
691 * @return string             Generated query
692 */
693function generate_query_values($value, $column, $condition = NULL, $leading_and = TRUE)
694{
695  //if (!is_array($value)) { $value = explode(',', $value); }
696  if (!is_array($value)) { $value = array((string)$value); }
697  $column = '`' . str_replace(array('`', '.'), array('', '`.`'), $column) . '`'; // I.column -> `I`.`column`
698  $condition = ($condition === TRUE ? 'LIKE' : strtoupper(trim($condition)));
699  if (str_contains($condition, ['NOT', '!=']))
700  {
701    $negative  = TRUE;
702    $condition = str_replace(array('NOT', '!=', ' '), '', $condition);
703  } else {
704    $negative  = FALSE;
705  }
706
707  $search  = array('%', '_');
708  $replace = array('\%', '\_');
709  $values  = array();
710  switch ($condition)
711  {
712    // Use LIKE condition
713    case 'LIKE':
714      // Replace (* by %) and (? by _) only for LIKE condition
715      $search[]  = '*'; // any string
716      $replace[] = '%';
717      $search[]  = '?'; // any single char
718      $replace[] = '_';
719    case '%LIKE%':
720    case '%LIKE':
721    case 'LIKE%':
722      if ($negative) { $implode = ' AND '; $like = ' NOT LIKE '; }
723      else           { $implode = ' OR ';  $like = ' LIKE '; }
724      foreach ($value as $v)
725      {
726        if ($v === '*')
727        {
728          $values = array("ISNULL($column, 1)" . $like . "'%'");
729          break;
730        }
731        else if ($v === '')
732        {
733          $values[] = "ISNULL($column, '')" . $like . "''";
734        } else {
735          $v = dbEscape($v); // Escape BEFORE replace!
736          $v = str_replace($search, $replace, $v);
737          $v = str_replace('LIKE', $v, $condition);
738          $values[] = $column . $like . "'" . $v . "'";
739        }
740      }
741      $values = array_unique($values); // Removes duplicate values
742      if (count($values))
743      {
744        $where = ' AND (' . implode($implode, $values) . ')';
745      } else {
746        // Empty values
747        $where  = ' AND ';
748        $where .= $negative ? '1' : '0';
749      }
750      break;
751    // Use NULL condition
752    case 'NULL':
753      $value = array_shift($value);
754      $value = ($negative) ? !$value : (bool)$value; // When required negative null condition (NOT NULL), reverse $value sign
755      //r($value);
756      if ($value) {
757        $where = ' AND ' . $column . ' IS NULL';
758      } else {
759        $where = ' AND ' . $column . ' IS NOT NULL';
760      }
761      break;
762    // Use IN condition
763    default:
764      $where = '';
765      foreach ($value as $v)
766      {
767        if ($v == OBS_VAR_UNSET || $v === '')
768        {
769          $add_null = TRUE; // Add check NULL values at end
770          $values[] = "''";
771        } else {
772          $values[] = "'" . dbEscape($v) . "'";
773        }
774      }
775      $count = count($values);
776      if ($count == 1)
777      {
778        $where .= $column . ($negative ? ' != ' : ' = ') . $values[0];
779      }
780      else if ($count)
781      {
782        $values = array_unique($values); // Removes duplicate values
783        $where .= $column . ($negative ? ' NOT IN (' : ' IN (') . implode(',', $values) . ')';
784      } else {
785        // Empty values
786        $where = $negative ? '1' : '0';
787      }
788      if ($add_null)
789      {
790        // Add search for empty values
791        if ($negative)
792        {
793          $where .= " AND $column IS NOT NULL";
794        } else {
795          $where .= " OR $column IS NULL";
796        }
797        $where = " AND ($where)";
798      } else {
799        $where = " AND " . $where;
800      }
801      break;
802  }
803  if(!$leading_and) { $where = preg_replace('/^(\ )+AND/', '', $where); }
804
805  return $where;
806}
807
808// EOF
809