Exemplo n.º 1
0
 /**
  * rcube:utils::tokenize_string()
  */
 function test_tokenize_string()
 {
     $test = array('' => array(), 'abc d' => array('abc'), 'abc de' => array('abc', 'de'), 'äàé;êöü-xyz' => array('äàé', 'êöü', 'xyz'), '日期格式' => array('日期格式'));
     foreach ($test as $input => $output) {
         $result = rcube_utils::tokenize_string($input);
         $this->assertSame($output, $result);
     }
 }
Exemplo n.º 2
0
 /**
  * Search contacts
  *
  * @param mixed   $fields   The field name or array of field names to search in
  * @param mixed   $value    Search value (or array of values when $fields is array)
  * @param int     $mode     Matching mode:
  *                          0 - partial (*abc*),
  *                          1 - strict (=),
  *                          2 - prefix (abc*)
  * @param boolean $select   True if results are requested, False if count only
  * @param boolean $nocount  True to skip the count query (select only)
  * @param array   $required List of fields that cannot be empty
  *
  * @return object rcube_result_set Contact records and 'count' value
  */
 function search($fields, $value, $mode = 0, $select = true, $nocount = false, $required = array())
 {
     if (!is_array($required) && !empty($required)) {
         $required = array($required);
     }
     $where = $and_where = $post_search = array();
     $mode = intval($mode);
     $WS = ' ';
     $AS = self::SEPARATOR;
     // 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) {
                         case 1:
                             // strict
                             $where[] = '(' . $this->db->quote_identifier($col) . ' = ' . $this->db->quote($val) . ' OR ' . $this->db->ilike($col, $val . $AS . '%') . ' OR ' . $this->db->ilike($col, '%' . $AS . $val . $AS . '%') . ' OR ' . $this->db->ilike($col, '%' . $AS . $val) . ')';
                             break;
                         case 2:
                             // prefix
                             $where[] = '(' . $this->db->ilike($col, $val . '%') . ' OR ' . $this->db->ilike($col, $AS . $val . '%') . ')';
                             break;
                         default:
                             // partial
                             $where[] = $this->db->ilike($col, '%' . $val . '%');
                     }
                 } else {
                     if (in_array($col, $this->fulltext_cols)) {
                         $where[] = $this->fulltext_sql_where($val, $mode, 'words');
                     }
                     $post_search[$col] = mb_strtolower($val);
                 }
             }
         } else {
             if ($fields == '*') {
                 $where[] = $this->fulltext_sql_where($value, $mode, 'words');
             } else {
                 // require each word in to be present in one of the fields
                 $words = $mode == 1 ? array($value) : rcube_utils::tokenize_string($value, 1);
                 foreach ($words 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(" AND ", $where);
     }
     if (!empty($and_where)) {
         $where = ($where ? "({$where}) AND " : '') . join(' AND ', $and_where);
     }
     // 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) || !empty($required)) {
         $ids = array(0);
         // build key name regexp
         $regexp = '/^(' . implode(array_keys($post_search), '|') . ')(?:.*)$/';
         // use initial WHERE clause, to limit records number if possible
         if (!empty($where)) {
             $this->set_search_set($where);
         }
         // count result pages
         $cnt = $this->count()->count;
         $pages = ceil($cnt / $this->page_size);
         $scnt = count($post_search);
         // get (paged) result
         for ($i = 0; $i < $pages; $i++) {
             $this->list_records(null, $i, true);
             while ($row = $this->result->next()) {
                 $id = $row[$this->primary_key];
                 $found = array();
                 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;
                         }
                     }
                 }
                 // all fields match
                 if (count($found) >= $scnt) {
                     $ids[] = $id;
                 }
             }
         }
         // build WHERE clause
         $ids = $this->db->array2list($ids, 'integer');
         $where = 'c.`' . $this->primary_key . '` IN (' . $ids . ')';
         // reset counter
         unset($this->cache['count']);
         // when we know we have an empty result
         if ($ids == '0') {
             $this->set_search_set($where);
             return $this->result = new rcube_result_set(0, 0);
         }
     }
     if (!empty($where)) {
         $this->set_search_set($where);
         if ($select) {
             $this->list_records(null, 0, $nocount);
         } else {
             $this->result = $this->count();
         }
     }
     return $this->result;
 }
Exemplo n.º 3
0
 /**
  * Compose an LDAP filter string matching all words from the search string
  * in the given list of attributes.
  *
  * @param string  $value    Search value
  * @param mixed   $attrs    List of LDAP attributes to search
  * @param int     $mode     Matching mode:
  *                          0 - partial (*abc*),
  *                          1 - strict (=),
  *                          2 - prefix (abc*)
  * @return string LDAP filter
  */
 public static function fulltext_search_filter($value, $attributes, $mode = 1)
 {
     if (empty($attributes)) {
         $attributes = array('cn');
     }
     $groups = array();
     $value = str_replace('*', '', $value);
     $words = $mode == 0 ? rcube_utils::tokenize_string($value, 1) : array($value);
     // set wildcards
     $wp = $ws = '';
     if ($mode != 1) {
         $ws = '*';
         $wp = !$mode ? '*' : '';
     }
     // search each word in all listed attributes
     foreach ($words as $word) {
         $parts = array();
         foreach ($attributes as $attr) {
             $parts[] = "({$attr}={$wp}" . self::quote_string($word) . "{$ws})";
         }
         $groups[] = '(|' . join('', $parts) . ')';
     }
     return count($groups) > 1 ? '(&' . join('', $groups) . ')' : join('', $groups);
 }
 /**
  * @param  integer Event's new start (unix timestamp)
  * @param  integer Event's new end (unix timestamp)
  * @param  string  Search query (optional)
  * @param  boolean Include virtual events (optional)
  * @param  array   Additional parameters to query storage
  * @param  array   Additional query to filter events
  * @return array A list of event records
  */
 public function list_events($start, $end, $search = null, $virtual = 1, $query = array(), $filter_query = null)
 {
     // convert to DateTime for comparisons
     // #5190: make the range a little bit wider
     // to workaround possible timezone differences
     try {
         $start = new DateTime('@' . ($start - 12 * 3600));
     } catch (Exception $e) {
         $start = new DateTime('@0');
     }
     try {
         $end = new DateTime('@' . ($end + 12 * 3600));
     } catch (Exception $e) {
         $end = new DateTime('today +10 years');
     }
     // get email addresses of the current user
     $user_emails = $this->cal->get_user_emails();
     // query Kolab storage
     $query[] = array('dtstart', '<=', $end);
     $query[] = array('dtend', '>=', $start);
     if (is_array($filter_query)) {
         $query = array_merge($query, $filter_query);
     }
     if (!empty($search)) {
         $search = mb_strtolower($search);
         $words = rcube_utils::tokenize_string($search, 1);
         foreach (rcube_utils::normalize_string($search, true) as $word) {
             $query[] = array('words', 'LIKE', $word);
         }
     } else {
         $words = array();
     }
     // set partstat filter to skip pending and declined invitations
     if (empty($filter_query) && $this->get_namespace() != 'other') {
         $partstat_exclude = array('NEEDS-ACTION', 'DECLINED');
     } else {
         $partstat_exclude = array();
     }
     $events = array();
     foreach ($this->storage->select($query) as $record) {
         $event = $this->_to_driver_event($record, !$virtual);
         // remember seen categories
         if ($event['categories']) {
             $cat = is_array($event['categories']) ? $event['categories'][0] : $event['categories'];
             $this->categories[$cat]++;
         }
         // list events in requested time window
         if ($event['start'] <= $end && $event['end'] >= $start) {
             unset($event['_attendees']);
             $add = true;
             // skip the first instance of a recurring event if listed in exdate
             if ($virtual && !empty($event['recurrence']['EXDATE'])) {
                 $event_date = $event['start']->format('Ymd');
                 $exdates = (array) $event['recurrence']['EXDATE'];
                 foreach ($exdates as $exdate) {
                     if ($exdate->format('Ymd') == $event_date) {
                         $add = false;
                         break;
                     }
                 }
             }
             // find and merge exception for the first instance
             if ($virtual && !empty($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS'])) {
                 foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
                     if ($event['_instance'] == $exception['_instance']) {
                         // clone date objects from main event before adjusting them with exception data
                         if (is_object($event['start'])) {
                             $event['start'] = clone $record['start'];
                         }
                         if (is_object($event['end'])) {
                             $event['end'] = clone $record['end'];
                         }
                         kolab_driver::merge_exception_data($event, $exception);
                     }
                 }
             }
             if ($add) {
                 $events[] = $event;
             }
         }
         // resolve recurring events
         if ($record['recurrence'] && $virtual == 1) {
             $events = array_merge($events, $this->get_recurring_events($record, $start, $end));
         } else {
             if (is_array($record['exceptions'])) {
                 foreach ($record['exceptions'] as $ex) {
                     $component = $this->_to_driver_event($ex);
                     if ($component['start'] <= $end && $component['end'] >= $start) {
                         $events[] = $component;
                     }
                 }
             }
         }
     }
     // post-filter all events by fulltext search and partstat values
     $me = $this;
     $events = array_filter($events, function ($event) use($words, $partstat_exclude, $user_emails, $me) {
         // fulltext search
         if (count($words)) {
             $hits = 0;
             foreach ($words as $word) {
                 $hits += $me->fulltext_match($event, $word, false);
             }
             if ($hits < count($words)) {
                 return false;
             }
         }
         // partstat filter
         if (count($partstat_exclude) && is_array($event['attendees'])) {
             foreach ($event['attendees'] as $attendee) {
                 if (in_array($attendee['email'], $user_emails) && in_array($attendee['status'], $partstat_exclude)) {
                     return false;
                 }
             }
         }
         return true;
     });
     // avoid session race conditions that will loose temporary subscriptions
     $this->cal->rc->session->nowrite = true;
     return $events;
 }