Class it_dbi:

/**
 * Create an SQL query (the stuff after 'WHERE') according to an array of selection criteria.
 *
 * Example:
 * $sql = $table->_where(array('Type' => 'bar',
 *    'Date >=' => '1999-01-01', '-Date <' => 'NOW()',
 *    'Status' => array('foo', 'bar', 'qux'),        # same as 'Status IN' => ...
 *    'User NI' => 'chris'), 'ORDER BY Date');
 *
 * @param $params optional array of fieldname => value tupels. These are ANDed to form a WHERE clause.
 *   fieldname can contain an operator (separated by space), the default operator is '='.
 *   The special operator 'NI' specifies that the argument must be contained in a comma-separated list.
 * @param $link DB link used to escape values (not used anymore)
 * @param $omit_where Do not add 'WHERE ' to result
 * @return The generated SQL clause
 * @see select()
 * @see iterate()
 */
function _where($params)
{
    $dyncols_enabled = !strlen($params['JOIN'] . $params['FROM']);
    unset($params['JOIN'], $params['FROM']);

    if (is_array($params) && (count($params) > 0))
    {
        $query = '';
        $stringquery = '';
        $sep = '';

        foreach($params as $field => $value)
        {
            if (is_int($field)) /* no key specified; just append */
            {
                if (strcasecmp($value, 'OR'))
                    $stringquery .= " $value";
                else
                    $sep = ' OR ';
            }
            else if ($field == "LIMIT")
            {
                if ($value !== false) # only false no null; uninitialized values should not unintentionally omit LIMIT
                    $stringquery .= " LIMIT " . (it::match('^[ ,\d]+$', $value) ?? it::error(['title' => "invalid LIMIT $value", 'body' => $params]) + 0);
            }
            else
            {
                $needs_where = true;

                if (!isset($value))
                {
                    $op = 'IS';
                    $qval = 'NULL';
                }
                else
                {
                    if (preg_match('/^(\S+)\s+(\S.*)$/', $field, $regs))
                    {
                        $field = $regs[1];
                        $op = strtoupper($regs[2]);
                    }
                    else
                        $op = is_array($value) ? 'IN' : '=';

                    # If field name starts with '-', the raw value is taken, no escaping is done and no quotes are put around it.
                    if (substr($field, 0, 1) == '-')
                    {
                        $field = substr($field, 1);    # Strip that '-' sign
                        $qval = $value;
                    }
                    else if (!is_array($value))
                        $qval = $this->escape_string((string)$value);
                }

                if ($dyncols_enabled && $this->_fields['dyncols'] && !$this->_fields[$field] && strpos($field, '(') === false)
                    $field = $this->_json_extract('dyncols', $field);
                else if (it::match('^\w*[A-Z]\w+$', $field, ['casesensitive' => 1]))
                    $field = $this->escape_name($field);

                switch ($op)
                {
                    case 'NI':
                        if ($value)
                        {
                            $parts = array();
                            foreach ((array)$value as $val)
                                $parts[] = "CONCAT(',',$field,',') LIKE " . $this->escape_string("%,$val,%");

                            $query .= $sep . "$field IS NOT NULL AND (" . implode(" OR ", $parts) . ")";    # Check for IS NOT NULL to take advantage of index
                        }
                        else
                            $query .= $sep . "TRUE";
                        break;

                    case 'MATCH':
                        $qval = implode(' ', (array)$value);
                        $query .= "$sep$op ($field) AGAINST (" . $this->escape_string($qval) . " IN BOOLEAN MODE)";
                        break;

                    case 'IN':
                    case 'NOT IN':
                        if (is_array($value))
                        {
                            if ($value)
                            {
                                $qvals = array();

                                foreach ($value as $val)
                                    $qvals[] = $this->escape_string($val);

                                $query .= "$sep$field $op (" . implode(",", $qvals) . ")";     # null is mapped to ''
                            }
                            else
                                $query .= $sep . (($op == 'IN') ? "FALSE" : "TRUE");

                            break;
                        }
                        /* FALLTHROUGH */

                    default:
                        if (isset($qval))
                            $query .= "$sep$field $op $qval";
                        else
                            it::fatal('Undefined $qval when constructing query due to invalid $value (array)');
                        break;
                }
                $sep = ' AND ';
            }
        }

        $query .= $stringquery;

        if ($needs_where)
            $query = "WHERE $query";
    }

    return $query;
}