private function convert_save_data($save_data, $record = array()) { $out = array(); $words = ''; // copy values into vcard object $vcard = new rcube_vcard($record['vcard'] ? $record['vcard'] : $save_data['vcard']); $vcard->reset(); foreach ($save_data as $key => $values) { list($field, $section) = explode(':', $key); $fulltext = in_array($field, $this->fulltext_cols); // avoid casting DateTime objects to array if (is_object($values) && is_a($values, 'DateTime')) { $values = array(0 => $values); } foreach ((array) $values as $value) { if (isset($value)) { $vcard->set($field, $value, $section); } if ($fulltext && is_array($value)) { $words .= ' ' . rcube_utils::normalize_string(join(" ", $value)); } else { if ($fulltext && strlen($value) >= 3) { $words .= ' ' . rcube_utils::normalize_string($value); } } } } $out['vcard'] = $vcard->export(false); foreach ($this->table_cols as $col) { $key = $col; if (!isset($save_data[$key])) { $key .= ':home'; } if (isset($save_data[$key])) { if (is_array($save_data[$key])) { $out[$col] = join(self::SEPARATOR, $save_data[$key]); } else { $out[$col] = $save_data[$key]; } } } // save all e-mails in database column $out['email'] = join(self::SEPARATOR, $vcard->email); // join words for fulltext search $out['words'] = join(" ", array_unique(explode(" ", $words))); return $out; }
/** * Normalize the given string for fulltext search. * Currently only optimized for Latin-1 characters; to be extended * * @param string Input string (UTF-8) * @return string Normalized string * @deprecated since 0.9-beta */ protected static function normalize_string($str) { return rcube_utils::normalize_string($str); }
/** * @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 try { $start = new DateTime('@' . $start); } catch (Exception $e) { $start = new DateTime('@0'); } try { $end = new DateTime('@' . $end); } 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); // add query to exclude pending/declined invitations if (empty($filter_query) && $this->get_namespace() != 'other') { foreach ($user_emails as $email) { $query[] = array('tags', '!=', 'x-partstat:' . $email . ':needs-action'); $query[] = array('tags', '!=', 'x-partstat:' . $email . ':declined'); } } else { if (is_array($filter_query)) { $query = array_merge($query, $filter_query); } } if (!empty($search)) { $search = mb_strtolower($search); foreach (rcube_utils::normalize_string($search, true) as $word) { $query[] = array('words', 'LIKE', $word); } } $events = array(); foreach ($this->storage->select($query) as $record) { // post-filter events to skip pending and declined invitations if (empty($filter_query) && is_array($record['attendees']) && $this->get_namespace() != 'other') { foreach ($record['attendees'] as $attendee) { if (in_array($attendee['email'], $user_emails) && in_array($attendee['status'], array('NEEDS-ACTION', 'DECLINED'))) { continue 2; } } } $event = $this->_to_rcube_event($record); $this->events[$event['id']] = $event; // remember seen categories if ($event['categories']) { $this->categories[$event['categories']]++; } // filter events by search query if (!empty($search)) { $hit = false; foreach ($this->search_fields as $col) { $sval = is_array($event[$col]) ? self::_complex2string($event[$col]) : $event[$col]; if (empty($sval)) { continue; } // do a simple substring matching (to be improved) $val = mb_strtolower($sval); if (strpos($val, $search) !== false) { $hit = true; break; } } if (!$hit) { // skip this event if not match with search term continue; } } // 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']) || !empty($event['recurrence']['EXCEPTIONS']))) { $event_date = $event['start']->format('Ymd'); $exdates = (array) $event['recurrence']['EXDATE']; // add dates from exceptions to list if (is_array($event['recurrence']['EXCEPTIONS'])) { foreach ($event['recurrence']['EXCEPTIONS'] as $exception) { $exdates[] = clone $exception['start']; } } foreach ($exdates as $exdate) { if ($exdate->format('Ymd') == $event_date) { $add = false; break; } } } if ($add) { $events[] = $event; } } // resolve recurring events if ($record['recurrence'] && $virtual == 1) { $events = array_merge($events, $this->get_recurring_events($record, $start, $end)); } } // avoid session race conditions that will loose temporary subscriptions $this->cal->rc->session->nowrite = true; return $events; }
/** * rcube:utils::normalize_string() */ function test_normalize_string() { $test = array('' => '', 'abc def' => 'abc def', 'ÇçäâàåæéêëèïîìÅÉöôòüûùÿøØáíóúñÑÁÂÀãÃÊËÈÍÎÏÓÔõÕÚÛÙýÝ' => 'ccaaaaaeeeeiiiaeooouuuyooaiounnaaaaaeeeiiioooouuuyy', 'ąáâäćçčéęëěíîłľĺńňóôöŕřśšşťţůúűüźžżýĄŚŻŹĆ' => 'aaaaccceeeeiilllnnooorrsssttuuuuzzzyaszzc', 'ßs' => 'sss', 'Xae' => 'xa', 'Xoe' => 'xo', 'Xue' => 'xu', '项目' => '项目'); // this test fails on PHP 5.3.3 if (PHP_VERSION_ID > 50303) { $test['ß'] = ''; $test['日'] = ''; } foreach ($test as $input => $output) { $result = rcube_utils::normalize_string($input); $this->assertSame($output, $result, "Error normalizing '{$input}'"); } }
/** * rcube:utils::normalize _string() */ function test_normalize_string() { $test = array('' => '', 'abc def' => 'abc def'); foreach ($test as $input => $output) { $result = rcube_utils::normalize_string($input); $this->assertSame($output, $result); } }
/** * rcube:utils::normalize_string() */ function test_normalize_string() { $test = array('' => '', 'abc def' => 'abc def', 'ÇçäâàåæéêëèïîìÅÉöôòüûùÿøØáíóúñÑÁÂÀãÃÊËÈÍÎÏÓÔõÕÚÛÙýÝ' => 'ccaaaaaeeeeiiiaeooouuuyooaiounnaaaaaeeeiiioooouuuyy', 'ąáâäćçčéęëěíîłľĺńňóôöŕřśšşťţůúűüźžżýĄŚŻŹĆ' => 'aaaaccceeeeiilllnnooorrsssttuuuuzzzyaszzc', 'ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u'); foreach ($test as $input => $output) { $result = rcube_utils::normalize_string($input); $this->assertSame($output, $result); } }
/** * Get all taks records matching the given filter * * @param array Hash array with filter criterias: * - mask: Bitmask representing the filter selection (check against tasklist::FILTER_MASK_* constants) * - from: Date range start as string (Y-m-d) * - to: Date range end as string (Y-m-d) * - search: Search query string * @param array List of lists to get tasks from * @return array List of tasks records matchin the criteria */ public function list_tasks($filter, $lists = null) { if (empty($lists)) { $lists = array_keys($this->lists); } else { if (is_string($lists)) { $lists = explode(',', $lists); } } $results = array(); // query Kolab storage $query = array(); if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE) { $query[] = array('tags', '~', 'x-complete'); } else { $query[] = array('tags', '!~', 'x-complete'); } // full text search (only works with cache enabled) if ($filter['search']) { $search = mb_strtolower($filter['search']); foreach (rcube_utils::normalize_string($search, true) as $word) { $query[] = array('words', '~', $word); } } foreach ($lists as $list_id) { $folder = $this->folders[$list_id]; foreach ((array) $folder->select($query) as $record) { $task = $this->_to_rcube_task($record); $task['list'] = $list_id; // TODO: post-filter tasks returned from storage $results[] = $task; } } return $results; }
/** * Get all taks records matching the given filter * * @param array Hash array with filter criterias: * - mask: Bitmask representing the filter selection (check against tasklist::FILTER_MASK_* constants) * - from: Date range start as string (Y-m-d) * - to: Date range end as string (Y-m-d) * - search: Search query string * @param array List of lists to get tasks from * @return array List of tasks records matchin the criteria */ public function list_tasks($filter, $lists = null) { if (empty($lists)) { $lists = array_keys($this->lists); } else { if (is_string($lists)) { $lists = explode(',', $lists); } } $results = array(); // query Kolab storage $query = array(); if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE) { $query[] = array('tags', '~', 'x-complete'); } else { if (empty($filter['since'])) { $query[] = array('tags', '!~', 'x-complete'); } } // full text search (only works with cache enabled) if ($filter['search']) { $search = mb_strtolower($filter['search']); foreach (rcube_utils::normalize_string($search, true) as $word) { $query[] = array('words', '~', $word); } } if ($filter['since']) { $query[] = array('changed', '>=', $filter['since']); } // load all tags into memory first kolab_storage_config::get_instance()->get_tags(); foreach ($lists as $list_id) { if (!($folder = $this->get_folder($list_id))) { continue; } foreach ($folder->select($query) as $record) { $this->load_tags($record); $task = $this->_to_rcube_task($record, $list_id); // TODO: post-filter tasks returned from storage $results[] = $task; } } // avoid session race conditions that will loose temporary subscriptions $this->plugin->rc->session->nowrite = true; return $results; }
/** * @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; }