Aleksander Machniak
2015-04-08 d61d668b64c44fc046095b807834c4836a8c05c5
program/lib/Roundcube/rcube_contacts.php
@@ -302,46 +302,27 @@
     */
    function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array())
    {
        if (!is_array($fields))
            $fields = array($fields);
        if (!is_array($required) && !empty($required))
            $required = array($required);
        $where = $and_where = array();
        $where = $and_where = $post_search = array();
        $mode = intval($mode);
        $WS = ' ';
        $AS = self::SEPARATOR;
        foreach ($fields as $idx => $col) {
            // direct ID search
            if ($col == 'ID' || $col == $this->primary_key) {
                $ids     = !is_array($value) ? explode(self::SEPARATOR, $value) : $value;
                $ids     = $this->db->array2list($ids, 'integer');
                $where[] = 'c.' . $this->primary_key.' IN ('.$ids.')';
                continue;
            }
            // fulltext search in all fields
            else if ($col == '*') {
                $words = array();
                foreach (explode($WS, rcube_utils::normalize_string($value)) as $word) {
                    switch ($mode) {
                    case 1: // strict
                        $words[] = '(' . $this->db->ilike('words', $word . '%')
                            . ' OR ' . $this->db->ilike('words', '%' . $WS . $word . $WS . '%')
                            . ' OR ' . $this->db->ilike('words', '%' . $WS . $word) . ')';
                        break;
                    case 2: // prefix
                        $words[] = '(' . $this->db->ilike('words', $word . '%')
                            . ' OR ' . $this->db->ilike('words', '%' . $WS . $word . '%') . ')';
                        break;
                    default: // partial
                        $words[] = $this->db->ilike('words', '%' . $word . '%');
                    }
                }
                $where[] = '(' . join(' AND ', $words) . ')';
            }
            else {
                $val = is_array($value) ? $value[$idx] : $value;
        // direct ID search
        if ($fields == 'ID' || $fields == $this->primary_key) {
            $ids     = !is_array($value) ? explode(self::SEPARATOR, $value) : $value;
            $ids     = $this->db->array2list($ids, 'integer');
            $where[] = 'c.' . $this->primary_key.' IN ('.$ids.')';
        }
        else if (is_array($value)) {
            foreach ((array)$fields as $idx => $col) {
                $val = $value[$idx];
                if (!strlen($val))
                    continue;
                // table column
                if (in_array($col, $this->table_cols)) {
                    switch ($mode) {
@@ -362,36 +343,35 @@
                // vCard field
                else {
                    if (in_array($col, $this->fulltext_cols)) {
                        foreach (rcube_utils::normalize_string($val, true) as $word) {
                            switch ($mode) {
                            case 1: // strict
                                $words[] = '(' . $this->db->ilike('words', $word . $WS . '%')
                                    . ' OR ' . $this->db->ilike('words', '%' . $AS . $word . $WS .'%')
                                    . ' OR ' . $this->db->ilike('words', '%' . $AS . $word) . ')';
                                break;
                            case 2: // prefix
                                $words[] = '(' . $this->db->ilike('words', $word . '%')
                                    . ' OR ' . $this->db->ilike('words', $AS . $word . '%') . ')';
                                break;
                            default: // partial
                                $words[] = $this->db->ilike('words', '%' . $word . '%');
                            }
                        }
                        $where[] = '(' . join(' AND ', $words) . ')';
                        $where[] = $this->fulltext_sql_where($val, $mode, 'words');
                    }
                    if (is_array($value))
                        $post_search[$col] = mb_strtolower($val);
                    $post_search[$col] = mb_strtolower($val);
                }
            }
        }
        // fulltext search in all fields
        else if ($fields == '*') {
            $where[] = $this->fulltext_sql_where($value, $mode, 'words');
        }
        else {
            // require each word in to be present in one of the fields
            foreach (rcube_utils::tokenize_string($value, 1) as $word) {
                $groups = array();
                foreach ((array)$fields as $idx => $col) {
                    $groups[] = $this->fulltext_sql_where($word, $mode, $col);
                }
                $where[] = '(' . join(' OR ', $groups) . ')';
            }
        }
        foreach (array_intersect($required, $this->table_cols) as $col) {
            $and_where[] = $this->db->quote_identifier($col).' <> '.$this->db->quote('');
        }
        $required = array_diff($required, $this->table_cols);
        if (!empty($where)) {
            // use AND operator for advanced searches
            $where = join(is_array($value) ? ' AND ' : ' OR ', $where);
            $where = join(" AND ", $where);
        }
        if (!empty($and_where))
@@ -399,7 +379,7 @@
        // Post-searching in vCard data fields
        // we will search in all records and then build a where clause for their IDs
        if (!empty($post_search)) {
        if (!empty($post_search) || !empty($required)) {
            $ids = array(0);
            // build key name regexp
            $regexp = '/^(' . implode(array_keys($post_search), '|') . ')(?:.*)$/';
@@ -408,7 +388,7 @@
                $this->set_search_set($where);
            // count result pages
            $cnt   = $this->count();
            $cnt   = $this->count()->count;
            $pages = ceil($cnt / $this->page_size);
            $scnt  = count($post_search);
@@ -418,14 +398,33 @@
                while ($row = $this->result->next()) {
                    $id    = $row[$this->primary_key];
                    $found = array();
                    foreach (preg_grep($regexp, array_keys($row)) as $col) {
                        $pos     = strpos($col, ':');
                        $colname = $pos ? substr($col, 0, $pos) : $col;
                        $search  = $post_search[$colname];
                        foreach ((array)$row[$col] as $value) {
                            if ($this->compare_search_value($colname, $value, $search, $mode)) {
                                $found[$colname] = true;
                                break 2;
                    if (!empty($post_search)) {
                        foreach (preg_grep($regexp, array_keys($row)) as $col) {
                            $pos     = strpos($col, ':');
                            $colname = $pos ? substr($col, 0, $pos) : $col;
                            $search  = $post_search[$colname];
                            foreach ((array)$row[$col] as $value) {
                                if ($this->compare_search_value($colname, $value, $search, $mode)) {
                                    $found[$colname] = true;
                                    break 2;
                                }
                            }
                        }
                    }
                    // check if required fields are present
                    if (!empty($required)) {
                        foreach ($required as $req) {
                            $hit = false;
                            foreach ($row as $c => $values) {
                                if ($c === $req || strpos($c, $req.':') === 0) {
                                    if ((is_string($row[$c]) && strlen($row[$c])) || !empty($row[$c])) {
                                        $hit = true;
                                        break;
                                    }
                                }
                            }
                            if (!$hit) {
                                continue 2;
                            }
                        }
                    }
@@ -460,6 +459,34 @@
        return $this->result;
    }
    /**
     * Helper method to compose SQL where statements for fulltext searching
     */
    private function fulltext_sql_where($value, $mode, $col = 'words', $bool = 'AND')
    {
        $WS = ' ';
        $AS = $col == 'words' ? $WS : self::SEPARATOR;
        $words = $col == 'words' ? rcube_utils::normalize_string($value, true) : array($value);
        $where = array();
        foreach ($words as $word) {
            switch ($mode) {
            case 1: // strict
                $where[] = '(' . $this->db->ilike($col, $word . '%')
                    . ' OR ' . $this->db->ilike($col, '%' . $WS . $word . $WS . '%')
                    . ' OR ' . $this->db->ilike($col, '%' . $WS . $word) . ')';
                break;
            case 2: // prefix
                $where[] = '(' . $this->db->ilike($col, $word . '%')
                    . ' OR ' . $this->db->ilike($col, '%' . $AS . $word . '%') . ')';
                break;
            default: // partial
                $where[] = $this->db->ilike($col, '%' . $word . '%');
            }
        }
        return count($where) ? '(' . join(" $bool ", $where) . ')' : '';
    }
    /**
     * Count number of available contacts in database
@@ -714,6 +741,11 @@
        // copy values into vcard object
        $vcard = new rcube_vcard($record['vcard'] ? $record['vcard'] : $save_data['vcard'], RCUBE_CHARSET, false, $this->vcard_fieldmap);
        $vcard->reset();
        // don't store groups in vCard (#1490277)
        $vcard->set('groups', null);
        unset($save_data['groups']);
        foreach ($save_data as $key => $values) {
            list($field, $section) = explode(':', $key);
            $fulltext = in_array($field, $this->fulltext_cols);
@@ -909,7 +941,7 @@
            $name, $gid, $this->user_id
        );
        return $this->db->affected_rows() ? $name : false;
        return $this->db->affected_rows($sql_result) ? $name : false;
    }
@@ -983,7 +1015,7 @@
            $group_id
        );
        return $this->db->affected_rows();
        return $this->db->affected_rows($sql_result);
    }