mirror of
https://github.com/roundcube/roundcubemail.git
synced 2026-03-03 06:44:03 +01:00
413 lines
13 KiB
PHP
413 lines
13 KiB
PHP
<?php
|
|
|
|
/**
|
|
+-----------------------------------------------------------------------+
|
|
| This file is part of the Roundcube Webmail client |
|
|
| |
|
|
| Copyright (C) The Roundcube Dev Team |
|
|
| |
|
|
| Licensed under the GNU General Public License version 3 or |
|
|
| any later version with exceptions for skins & plugins. |
|
|
| See the README file for a full license statement. |
|
|
| |
|
|
| PURPOSE: |
|
|
| Interface to the collected addresses database |
|
|
+-----------------------------------------------------------------------+
|
|
| Author: Aleksander Machniak <alec@alec.pl> |
|
|
+-----------------------------------------------------------------------+
|
|
*/
|
|
|
|
/**
|
|
* Collected addresses database
|
|
*
|
|
* @package Framework
|
|
* @subpackage Addressbook
|
|
*/
|
|
class rcube_addresses extends rcube_contacts
|
|
{
|
|
protected $db_name = 'collected_addresses';
|
|
protected $type = 0;
|
|
protected $table_cols = ['name', 'email'];
|
|
protected $fulltext_cols = ['name'];
|
|
|
|
// public properties
|
|
public $primary_key = 'address_id';
|
|
public $readonly = true;
|
|
public $groups = false;
|
|
public $undelete = false;
|
|
public $deletable = true;
|
|
public $coltypes = ['name', 'email'];
|
|
public $date_cols = [];
|
|
|
|
|
|
/**
|
|
* Object constructor
|
|
*
|
|
* @param object $dbconn Instance of the rcube_db class
|
|
* @param int $user User-ID
|
|
* @param int $type Type of the address (1 - recipient, 2 - trusted sender)
|
|
*/
|
|
public function __construct($dbconn, $user, $type)
|
|
{
|
|
$this->db = $dbconn;
|
|
$this->user_id = $user;
|
|
$this->type = $type;
|
|
$this->ready = $this->db && !$this->db->is_error();
|
|
}
|
|
|
|
/**
|
|
* Returns addressbook name
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_name()
|
|
{
|
|
if ($this->type == self::TYPE_RECIPIENT) {
|
|
return rcube::get_instance()->gettext('collectedrecipients');
|
|
}
|
|
|
|
if ($this->type == self::TYPE_TRUSTED_SENDER) {
|
|
return rcube::get_instance()->gettext('trustedsenders');
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* List the current set of contact records
|
|
*
|
|
* @param array $cols List of cols to show, Null means all
|
|
* @param int $subset Only return this number of records, use negative values for tail
|
|
* @param bool $nocount True to skip the count query (select only)
|
|
*
|
|
* @return array Indexed list of contact records, each a hash array
|
|
*/
|
|
public function list_records($cols = null, $subset = 0, $nocount = false)
|
|
{
|
|
if ($nocount || $this->list_page <= 1) {
|
|
// create dummy result, we don't need a count now
|
|
$this->result = new rcube_result_set();
|
|
}
|
|
else {
|
|
// count all records
|
|
$this->result = $this->count();
|
|
}
|
|
|
|
$start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first;
|
|
$length = $subset != 0 ? abs($subset) : $this->page_size;
|
|
|
|
$sql_result = $this->db->limitquery(
|
|
"SELECT * FROM " . $this->db->table_name($this->db_name, true)
|
|
. " WHERE `user_id` = ? AND `type` = ?"
|
|
. ($this->filter ? " AND ".$this->filter : "")
|
|
. " ORDER BY `name` " . $this->sort_order . ", `email` " . $this->sort_order,
|
|
$start_row,
|
|
$length,
|
|
$this->user_id,
|
|
$this->type
|
|
);
|
|
|
|
while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
|
|
$sql_arr['ID'] = $sql_arr[$this->primary_key];
|
|
$this->result->add($sql_arr);
|
|
}
|
|
|
|
$cnt = count($this->result->records);
|
|
|
|
// update counter
|
|
if ($nocount) {
|
|
$this->result->count = $cnt;
|
|
}
|
|
else if ($this->list_page <= 1) {
|
|
if ($cnt < $this->page_size && $subset == 0) {
|
|
$this->result->count = $cnt;
|
|
}
|
|
else if (isset($this->cache['count'])) {
|
|
$this->result->count = $this->cache['count'];
|
|
}
|
|
else {
|
|
$this->result->count = $this->_count();
|
|
}
|
|
}
|
|
|
|
return $this->result;
|
|
}
|
|
|
|
/**
|
|
* 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 Search mode. Sum of rcube_addressbook::SEARCH_*
|
|
* @param bool $select True if results are requested, False if count only
|
|
* @param bool $nocount True to skip the count query (select only)
|
|
* @param array $required List of fields that cannot be empty
|
|
*
|
|
* @return rcube_result_set Contact records and 'count' value
|
|
*/
|
|
public function search($fields, $value, $mode = 0, $select = true, $nocount = false, $required = [])
|
|
{
|
|
if (!is_array($required) && !empty($required)) {
|
|
$required = [$required];
|
|
}
|
|
|
|
$where = $post_search = [];
|
|
$mode = intval($mode);
|
|
|
|
// 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[] = $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 ($col == 'email' && ($mode & rcube_addressbook::SEARCH_STRICT)) {
|
|
$where[] = $this->db->ilike($col, $val);
|
|
}
|
|
else if (in_array($col, $this->table_cols)) {
|
|
$where[] = $this->fulltext_sql_where($val, $mode, $col);
|
|
}
|
|
else {
|
|
$where[] = '1 = 0'; // unsupported column
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// fulltext search in all fields
|
|
if ($fields == '*') {
|
|
$fields = ['name', 'email'];
|
|
}
|
|
|
|
// require each word in to be present in one of the fields
|
|
$words = ($mode & rcube_addressbook::SEARCH_STRICT) ? [$value] : rcube_utils::tokenize_string($value, 1);
|
|
foreach ($words as $word) {
|
|
$groups = [];
|
|
foreach ((array) $fields as $idx => $col) {
|
|
if ($col == 'email' && ($mode & rcube_addressbook::SEARCH_STRICT)) {
|
|
$groups[] = $this->db->ilike($col, $word);
|
|
}
|
|
else if (in_array($col, $this->table_cols)) {
|
|
$groups[] = $this->fulltext_sql_where($word, $mode, $col);
|
|
}
|
|
}
|
|
$where[] = '(' . implode(' OR ', $groups) . ')';
|
|
}
|
|
}
|
|
|
|
foreach (array_intersect($required, $this->table_cols) as $col) {
|
|
$where[] = $this->db->quote_identifier($col) . ' <> ' . $this->db->quote('');
|
|
}
|
|
|
|
if (!empty($where)) {
|
|
// use AND operator for advanced searches
|
|
$where = implode(' AND ', $where);
|
|
|
|
$this->set_search_set($where);
|
|
|
|
if ($select) {
|
|
$this->list_records(null, 0, $nocount);
|
|
}
|
|
else {
|
|
$this->result = $this->count();
|
|
}
|
|
}
|
|
else {
|
|
$this->result = new rcube_result_set();
|
|
}
|
|
|
|
return $this->result;
|
|
}
|
|
|
|
/**
|
|
* Count number of available contacts in database
|
|
*
|
|
* @return int Contacts count
|
|
*/
|
|
protected function _count()
|
|
{
|
|
// count contacts for this user
|
|
$sql_result = $this->db->query(
|
|
"SELECT COUNT(`address_id`) AS cnt"
|
|
. " FROM " . $this->db->table_name($this->db_name, true)
|
|
. " WHERE `user_id` = ? AND `type` = ?"
|
|
. ($this->filter ? " AND (" . $this->filter . ")" : ""),
|
|
$this->user_id,
|
|
$this->type
|
|
);
|
|
|
|
$sql_arr = $this->db->fetch_assoc($sql_result);
|
|
|
|
$this->cache['count'] = (int) $sql_arr['cnt'];
|
|
|
|
return $this->cache['count'];
|
|
}
|
|
|
|
/**
|
|
* Get a specific contact record
|
|
*
|
|
* @param mixed $id Record identifier(s)
|
|
* @param bool $assoc Enables returning associative array
|
|
*
|
|
* @return rcube_result_set|array Result object with all record fields
|
|
*/
|
|
function get_record($id, $assoc = false)
|
|
{
|
|
// return cached result
|
|
if ($this->result && ($first = $this->result->first()) && $first[$this->primary_key] == $id) {
|
|
return $assoc ? $first : $this->result;
|
|
}
|
|
|
|
$this->db->query(
|
|
"SELECT * FROM " . $this->db->table_name($this->db_name, true)
|
|
. " WHERE `address_id` = ? AND `user_id` = ?",
|
|
$id,
|
|
$this->user_id
|
|
);
|
|
|
|
$this->result = null;
|
|
|
|
if ($record = $this->db->fetch_assoc()) {
|
|
$record['ID'] = $record['address_id'];
|
|
$this->result = new rcube_result_set(1);
|
|
$this->result->add($record);
|
|
}
|
|
|
|
return $assoc && !empty($record) ? $record : $this->result;
|
|
}
|
|
|
|
/**
|
|
* Check the given data before saving.
|
|
* If input not valid, the message to display can be fetched using get_error()
|
|
*
|
|
* @param array &$save_data Associative array with data to save
|
|
* @param bool $autofix Try to fix/complete record automatically
|
|
*
|
|
* @return bool True if input is valid, False if not.
|
|
*/
|
|
public function validate(&$save_data, $autofix = false)
|
|
{
|
|
$email = array_filter($this->get_col_values('email', $save_data, true));
|
|
|
|
// require email
|
|
if (empty($email) || count($email) > 1) {
|
|
$this->set_error(self::ERROR_VALIDATE, 'noemailwarning');
|
|
return false;
|
|
}
|
|
|
|
$email = $email[0];
|
|
|
|
// check validity of the email address
|
|
if (!rcube_utils::check_email(rcube_utils::idn_to_ascii($email))) {
|
|
$rcube = rcube::get_instance();
|
|
$error = $rcube->gettext(['name' => 'emailformaterror', 'vars' => ['email' => $email]]);
|
|
$this->set_error(self::ERROR_VALIDATE, $error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Create a new contact record
|
|
*
|
|
* @param array $save_data Associative array with save data
|
|
* @param bool $check Enables validity checks
|
|
*
|
|
* @return int|bool The created record ID on success, False on error
|
|
*/
|
|
function insert($save_data, $check = false)
|
|
{
|
|
if (!is_array($save_data)) {
|
|
return false;
|
|
}
|
|
|
|
if ($check && ($existing = $this->search('email', $save_data['email'], false, false))) {
|
|
if ($existing->count) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
$this->cache = null;
|
|
|
|
$this->db->query(
|
|
"INSERT INTO " . $this->db->table_name($this->db_name, true)
|
|
. " (`user_id`, `changed`, `type`, `name`, `email`)"
|
|
. " VALUES (?, " . $this->db->now() . ", ?, ?, ?)",
|
|
$this->user_id,
|
|
$this->type,
|
|
$save_data['name'],
|
|
$save_data['email']
|
|
);
|
|
|
|
return $this->db->insert_id($this->db_name);
|
|
}
|
|
|
|
/**
|
|
* Update a specific contact record
|
|
*
|
|
* @param mixed $id Record identifier
|
|
* @param array $save_cols Associative array with save data
|
|
*
|
|
* @return bool True on success, False on error
|
|
*/
|
|
function update($id, $save_cols)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Delete one or more contact records
|
|
*
|
|
* @param array $ids Record identifiers
|
|
* @param bool $force Remove record(s) irreversible (unsupported)
|
|
*
|
|
* @return int|false Number of removed records
|
|
*/
|
|
function delete($ids, $force = true)
|
|
{
|
|
if (!is_array($ids)) {
|
|
$ids = explode(self::SEPARATOR, $ids);
|
|
}
|
|
|
|
$ids = $this->db->array2list($ids, 'integer');
|
|
|
|
// flag record as deleted (always)
|
|
$this->db->query(
|
|
"DELETE FROM " . $this->db->table_name($this->db_name, true)
|
|
. " WHERE `user_id` = ? AND `type` = ? AND `address_id` IN ($ids)",
|
|
$this->user_id, $this->type
|
|
);
|
|
|
|
$this->cache = null;
|
|
|
|
return $this->db->affected_rows();
|
|
}
|
|
|
|
/**
|
|
* Remove all records from the database
|
|
*
|
|
* @param bool $with_groups Remove also groups
|
|
*
|
|
* @return int Number of removed records
|
|
*/
|
|
function delete_all($with_groups = false)
|
|
{
|
|
$this->db->query("DELETE FROM " . $this->db->table_name($this->db_name, true)
|
|
. " WHERE `user_id` = ? AND `type` = ?",
|
|
$this->user_id, $this->type
|
|
);
|
|
|
|
$this->cache = null;
|
|
|
|
return $this->db->affected_rows();
|
|
}
|
|
}
|