Merge branch devel-addressbook (r4193:4382) back into trunk

release-0.6
thomascube 14 years ago
parent e81a30752b
commit 0501b637a3

@ -308,6 +308,15 @@ $rcmail_config['identities_level'] = 0;
// mime magic database // mime magic database
$rcmail_config['mime_magic'] = '/usr/share/misc/magic'; $rcmail_config['mime_magic'] = '/usr/share/misc/magic';
// path to imagemagick identify binary
$rcmail_config['im_identify_path'] = null;
// path to imagemagick convert binary
$rcmail_config['im_convert_path'] = null;
// maximum size of uploaded contact photos in pixel
$rcmail_config['contact_photo_size'] = 160;
// Enable DNS checking for e-mail address validation // Enable DNS checking for e-mail address validation
$rcmail_config['email_dns_check'] = false; $rcmail_config['email_dns_check'] = false;
@ -346,6 +355,9 @@ $rcmail_config['date_long'] = 'd.m.Y H:i';
// use this format for today's date display (date or strftime format) // use this format for today's date display (date or strftime format)
$rcmail_config['date_today'] = 'H:i'; $rcmail_config['date_today'] = 'H:i';
// use this format for date display without time (date or strftime format)
$rcmail_config['date_format'] = 'Y-m-d';
// store draft message is this mailbox // store draft message is this mailbox
// leave blank if draft messages should not be stored // leave blank if draft messages should not be stored
$rcmail_config['drafts_mbox'] = 'Drafts'; $rcmail_config['drafts_mbox'] = 'Drafts';
@ -458,7 +470,6 @@ $rcmail_config['ldap_public']['Verisign'] = array(
// The login name is used to search for the DN to bind with // The login name is used to search for the DN to bind with
'search_base_dn' => '', 'search_base_dn' => '',
'search_filter' => '', // e.g. '(&(objectClass=posixAccount)(uid=%u))' 'search_filter' => '', // e.g. '(&(objectClass=posixAccount)(uid=%u))'
'writable' => false, // Indicates if we can write to the LDAP directory or not. 'writable' => false, // Indicates if we can write to the LDAP directory or not.
// If writable is true then these fields need to be populated: // If writable is true then these fields need to be populated:
// LDAP_Object_Classes, required_fields, LDAP_rdn // LDAP_Object_Classes, required_fields, LDAP_rdn
@ -467,10 +478,21 @@ $rcmail_config['ldap_public']['Verisign'] = array(
'LDAP_rdn' => 'mail', // The RDN field that is used for new entries, this field needs to be one of the search_fields, the base of base_dn is appended to the RDN to insert into the LDAP directory. 'LDAP_rdn' => 'mail', // The RDN field that is used for new entries, this field needs to be one of the search_fields, the base of base_dn is appended to the RDN to insert into the LDAP directory.
'ldap_version' => 3, // using LDAPv3 'ldap_version' => 3, // using LDAPv3
'search_fields' => array('mail', 'cn'), // fields to search in 'search_fields' => array('mail', 'cn'), // fields to search in
'name_field' => 'cn', // this field represents the contact's name 'fieldmap' => array( // mapping of contact fields to directory attributes
'email_field' => 'mail', // this field represents the contact's e-mail // Roundcube => LDAP
'surname_field' => 'sn', // this field represents the contact's last name 'name' => 'cn',
'firstname_field' => 'gn', // this field represents the contact's first name 'surname' => 'sn',
'firstname' => 'givenName',
'email' => 'mail',
'phone:home' => 'homePhone',
'phone:work' => 'telephoneNumber',
'phone:mobile' => 'mobile',
'street' => 'street',
'zipcode' => 'postalCode',
'locality' => 'l',
'country' => 'c',
'organization' => 'o',
),
'sort' => 'cn', // The field to sort the listing by. 'sort' => 'cn', // The field to sort the listing by.
'scope' => 'sub', // search mode: sub|base|list 'scope' => 'sub', // search mode: sub|base|list
'filter' => '', // used for basic listing (if not empty) and will be &'d with search queries. example: status=act 'filter' => '', // used for basic listing (if not empty) and will be &'d with search queries. example: status=act
@ -489,6 +511,10 @@ $rcmail_config['autocomplete_addressbooks'] = array('sql');
// may need to do lengthy results building given overly-broad searches // may need to do lengthy results building given overly-broad searches
$rcmail_config['autocomplete_min_length'] = 1; $rcmail_config['autocomplete_min_length'] = 1;
// show address fields in this order
// available placeholders: {street}, {locality}, {zipcode}, {country}, {region}
$rcmail_config['address_template'] = '{street}<br/>{locality} {zipcode}<br/>{country} {region}';
// ---------------------------------- // ----------------------------------
// USER PREFERENCES // USER PREFERENCES
// ---------------------------------- // ----------------------------------

@ -71,6 +71,9 @@ class html
*/ */
public static function tag($tagname, $attrib = array(), $content = null, $allowed_attrib = null) public static function tag($tagname, $attrib = array(), $content = null, $allowed_attrib = null)
{ {
if (is_string($attrib))
$attrib = array('class' => $attrib);
$inline_tags = array('a','span','img'); $inline_tags = array('a','span','img');
$suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : ''; $suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : '';
@ -147,7 +150,7 @@ class html
$attr = array('href' => $attr); $attr = array('href' => $attr);
} }
return self::tag('a', $attr, $cont, array_merge(self::$common_attrib, return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
array('href','target','name','onclick','onmouseover','onmouseout','onmousedown','onmouseup'))); array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
} }
/** /**
@ -501,7 +504,7 @@ class html_select extends html
protected $tagname = 'select'; protected $tagname = 'select';
protected $options = array(); protected $options = array();
protected $allowed = array('name','size','tabindex','autocomplete', protected $allowed = array('name','size','tabindex','autocomplete',
'multiple','onchange','disabled'); 'multiple','onchange','disabled','rel');
/** /**
* Add a new option to this drop-down * Add a new option to this drop-down

@ -799,7 +799,7 @@ function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
// format each col // format each col
foreach ($a_show_cols as $col) foreach ($a_show_cols as $col)
$table->add($col, Q($row_data[$col])); $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
$c++; $c++;
} }
@ -819,32 +819,43 @@ function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
* @return string HTML field definition * @return string HTML field definition
*/ */
function rcmail_get_edit_field($col, $value, $attrib, $type='text') function rcmail_get_edit_field($col, $value, $attrib, $type='text')
{ {
static $colcounts = array();
$fname = '_'.$col; $fname = '_'.$col;
$attrib['name'] = $fname; $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
$attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
if ($type=='checkbox') if ($type == 'checkbox') {
{
$attrib['value'] = '1'; $attrib['value'] = '1';
$input = new html_checkbox($attrib); $input = new html_checkbox($attrib);
} }
else if ($type=='textarea') else if ($type == 'textarea') {
{
$attrib['cols'] = $attrib['size']; $attrib['cols'] = $attrib['size'];
$input = new html_textarea($attrib); $input = new html_textarea($attrib);
} }
else else if ($type == 'select') {
$input = new html_select($attrib);
$input->add('---', '');
$input->add(array_values($attrib['options']), array_keys($attrib['options']));
}
else {
if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
$attrib['type'] = 'text';
$input = new html_inputfield($attrib); $input = new html_inputfield($attrib);
}
// use value from post // use value from post
if (!empty($_POST[$fname])) if (isset($_POST[$fname])) {
$value = get_input_value($fname, RCUBE_INPUT_POST, $postvalue = get_input_value($fname, RCUBE_INPUT_POST,
$type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false); $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
$value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
}
$out = $input->show($value); $out = $input->show($value);
return $out; return $out;
} }
/** /**

@ -114,7 +114,7 @@ class rcmail
public $comm_path = './'; public $comm_path = './';
private $texts; private $texts;
private $books = array(); private $address_books = array();
private $action_map = array(); private $action_map = array();
@ -331,6 +331,10 @@ class rcmail
if ($plugin['instance'] instanceof rcube_addressbook) { if ($plugin['instance'] instanceof rcube_addressbook) {
$contacts = $plugin['instance']; $contacts = $plugin['instance'];
} }
// use existing instance
else if (isset($this->address_books[$id]) && is_a($this->address_books[$id], 'rcube_addressbook') && (!$writeable || !$this->address_books[$id]->readonly)) {
$contacts = $this->address_books[$id];
}
else if ($id && $ldap_config[$id]) { else if ($id && $ldap_config[$id]) {
$contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host'])); $contacts = new rcube_ldap($ldap_config[$id], $this->config->get('ldap_debug'), $this->config->mail_domain($_SESSION['imap_host']));
} }
@ -351,8 +355,8 @@ class rcmail
} }
// add to the 'books' array for shutdown function // add to the 'books' array for shutdown function
if (!in_array($contacts, $this->books)) if (!isset($this->address_books[$id]))
$this->books[] = $contacts; $this->address_books[$id] = $contacts;
return $contacts; return $contacts;
} }
@ -373,11 +377,12 @@ class rcmail
// We are using the DB address book // We are using the DB address book
if ($abook_type != 'ldap') { if ($abook_type != 'ldap') {
$contacts = new rcube_contacts($this->db, null); if (!isset($this->address_books['0']))
$this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID);
$list['0'] = array( $list['0'] = array(
'id' => 0, 'id' => '0',
'name' => rcube_label('personaladrbook'), 'name' => rcube_label('personaladrbook'),
'groups' => $contacts->groups, 'groups' => $this->address_books['0']->groups,
'readonly' => false, 'readonly' => false,
'autocomplete' => in_array('sql', $autocomplete) 'autocomplete' => in_array('sql', $autocomplete)
); );
@ -398,14 +403,15 @@ class rcmail
$plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list)); $plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
$list = $plugin['sources']; $list = $plugin['sources'];
if ($writeable && !empty($list)) { foreach ($list as $idx => $item) {
foreach ($list as $idx => $item) { // register source for shutdown function
if ($item['readonly']) { if (!is_object($this->address_books[$item['id']]))
$this->address_books[$item['id']] = $item;
// remove from list if not writeable as requested
if ($writeable && $item['readonly'])
unset($list[$idx]); unset($list[$idx]);
}
}
} }
return $list; return $list;
} }
@ -1078,9 +1084,12 @@ class rcmail
if (is_object($this->smtp)) if (is_object($this->smtp))
$this->smtp->disconnect(); $this->smtp->disconnect();
foreach ($this->books as $book) foreach ($this->address_books as $book) {
if (is_object($book)) if (!is_object($book)) // maybe an address book instance wasn't fetched using get_address_book() yet
$book = $this->get_address_book($book['id']);
if (is_a($book, 'rcube_addressbook'))
$book->close(); $book->close();
}
// before closing the database connection, write session data // before closing the database connection, write session data
if ($_SERVER['REMOTE_ADDR']) if ($_SERVER['REMOTE_ADDR'])
@ -1306,6 +1315,112 @@ class rcmail
} }
/**
* Use imagemagick or GD lib to read image properties
*
* @param string Absolute file path
* @return mixed Hash array with image props like type, width, height or False on error
*/
public static function imageprops($filepath)
{
$rcmail = rcmail::get_instance();
if ($cmd = $rcmail->config->get('im_identify_path', false)) {
list(, $type, $size) = explode(' ', strtolower(rcmail::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath))));
if ($size)
list($width, $height) = explode('x', $size);
}
else if (function_exists('getimagesize')) {
$imsize = @getimagesize($filepath);
$width = $imsize[0];
$height = $imsize[1];
$type = preg_replace('!image/!', '', $imsize['mime']);
}
return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false;
}
/**
* Convert an image to a given size and type using imagemagick (ensures input is an image)
*
* @param $p['in'] Input filename (mandatory)
* @param $p['out'] Output filename (mandatory)
* @param $p['size'] Width x height of resulting image, e.g. "160x60"
* @param $p['type'] Output file type, e.g. "jpg"
* @param $p['-opts'] Custom command line options to ImageMagick convert
* @return Success of convert as true/false
*/
public static function imageconvert($p)
{
$result = false;
$rcmail = rcmail::get_instance();
$convert = $rcmail->config->get('im_convert_path', false);
$identify = $rcmail->config->get('im_identify_path', false);
// imagemagick is required for this
if (!$convert)
return false;
if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false))))
list(, $type) = explode(' ', strtolower(rcmail::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps
$type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
$p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75);
$p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts'];
if (in_array($type, explode(',', $p['types']))) # Valid type?
$result = rcmail::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === "";
return $result;
}
/**
* Construct shell command, execute it and return output as string.
* Keywords {keyword} are replaced with arguments
*
* @param $cmd Format string with {keywords} to be replaced
* @param $values (zero, one or more arrays can be passed)
* @return output of command. shell errors not detectable
*/
public static function exec(/* $cmd, $values1 = array(), ... */)
{
$args = func_get_args();
$cmd = array_shift($args);
$values = $replacements = array();
// merge values into one array
foreach ($args as $arg)
$values += (array)$arg;
preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
foreach ($matches as $tags) {
list(, $tag, $option, $key) = $tags;
$parts = array();
if ($option) {
foreach ((array)$values["-$key"] as $key => $value) {
if ($value === true || $value === false || $value === null)
$parts[] = $value ? $key : "";
else foreach ((array)$value as $val)
$parts[] = "$key " . escapeshellarg($val);
}
}
else {
foreach ((array)$values[$key] as $value)
$parts[] = escapeshellarg($value);
}
$replacements[$tag] = join(" ", $parts);
}
// use strtr behaviour of going through source string once
$cmd = strtr($cmd, $replacements);
return (string)shell_exec($cmd);
}
/** /**
* Helper method to set a cookie with the current path and host settings * Helper method to set a cookie with the current path and host settings
* *

@ -5,7 +5,7 @@
| program/include/rcube_addressbook.php | | program/include/rcube_addressbook.php |
| | | |
| This file is part of the Roundcube Webmail client | | This file is part of the Roundcube Webmail client |
| Copyright (C) 2006-2009, The Roundcube Dev Team | | Copyright (C) 2006-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL | | Licensed under the GNU GPL |
| | | |
| PURPOSE: | | PURPOSE: |
@ -27,13 +27,22 @@
*/ */
abstract class rcube_addressbook abstract class rcube_addressbook
{ {
/** public properties */ /** constants for error reporting **/
var $primary_key; const ERROR_READ_ONLY = 1;
var $groups = false; const ERROR_NO_CONNECTION = 2;
var $readonly = true; const ERROR_INCOMPLETE = 3;
var $ready = false; const ERROR_SAVING = 4;
var $list_page = 1;
var $page_size = 10; /** public properties (mandatory) */
public $primary_key;
public $groups = false;
public $readonly = true;
public $ready = false;
public $list_page = 1;
public $page_size = 10;
public $coltypes = array('name' => array('limit'=>1), 'firstname' => array('limit'=>1), 'surname' => array('limit'=>1), 'email' => array('limit'=>1));
protected $error;
/** /**
* Save a search string for future listings * Save a search string for future listings
@ -54,6 +63,16 @@ abstract class rcube_addressbook
*/ */
abstract function reset(); abstract function reset();
/**
* Refresh saved search set after data has changed
*
* @return mixed New search set
*/
function refresh_search()
{
return $this->get_search_set();
}
/** /**
* List the current set of contact records * List the current set of contact records
* *
@ -69,9 +88,11 @@ abstract class rcube_addressbook
* @param array List of fields to search in * @param array List of fields to search in
* @param string Search value * @param string Search value
* @param boolean True if results are requested, False if count only * @param boolean True if results are requested, False if count only
* @return Indexed list of contact records and 'count' value * @param boolean True to skip the count query (select only)
* @param array List of fields that cannot be empty
* @return object rcube_result_set List of contact records and 'count' value
*/ */
abstract function search($fields, $value, $strict=false, $select=true); abstract function search($fields, $value, $strict=false, $select=true, $nocount=false, $required=array());
/** /**
* Count number of available contacts in database * Count number of available contacts in database
@ -97,6 +118,27 @@ abstract class rcube_addressbook
*/ */
abstract function get_record($id, $assoc=false); abstract function get_record($id, $assoc=false);
/**
* Returns the last error occured (e.g. when updating/inserting failed)
*
* @return array Hash array with the following fields: type, message
*/
function get_error()
{
return $this->error;
}
/**
* Setter for errors for internal use
*
* @param int Error type (one of this class' error constants)
* @param string Error message (name of a text label)
*/
protected function set_error($type, $message)
{
$this->error = array('type' => $type, 'message' => $message);
}
/** /**
* Close connection to source * Close connection to source
* Called on script shutdown * Called on script shutdown
@ -129,6 +171,8 @@ abstract class rcube_addressbook
* Create a new contact record * Create a new contact record
* *
* @param array Assoziative array with save data * @param array Assoziative array with save data
* Keys: Field name with optional section in the form FIELD:SECTION
* Values: Field value. Can be either a string or an array of strings for multiple values
* @param boolean True to check for duplicates first * @param boolean True to check for duplicates first
* @return mixed The created record ID on success, False on error * @return mixed The created record ID on success, False on error
*/ */
@ -137,11 +181,32 @@ abstract class rcube_addressbook
/* empty for read-only address books */ /* empty for read-only address books */
} }
/**
* Create new contact records for every item in the record set
*
* @param object rcube_result_set Recordset to insert
* @param boolean True to check for duplicates first
* @return array List of created record IDs
*/
function insertMultiple($recset, $check=false)
{
$ids = array();
if (is_object($recset) && is_a($recset, rcube_result_set)) {
while ($row = $recset->next()) {
if ($insert = $this->insert($row, $check))
$ids[] = $insert;
}
}
return $ids;
}
/** /**
* Update a specific contact record * Update a specific contact record
* *
* @param mixed Record identifier * @param mixed Record identifier
* @param array Assoziative array with save data * @param array Assoziative array with save data
* Keys: Field name with optional section in the form FIELD:SECTION
* Values: Field value. Can be either a string or an array of strings for multiple values
* @return boolean True on success, False on error * @return boolean True on success, False on error
*/ */
function update($id, $save_cols) function update($id, $save_cols)
@ -176,9 +241,10 @@ abstract class rcube_addressbook
/** /**
* List all active contact groups of this source * List all active contact groups of this source
* *
* @param string Optional search string to match group name
* @return array Indexed list of contact groups, each a hash array * @return array Indexed list of contact groups, each a hash array
*/ */
function list_groups() function list_groups($search = null)
{ {
/* empty for address books don't supporting groups */ /* empty for address books don't supporting groups */
return array(); return array();
@ -260,5 +326,34 @@ abstract class rcube_addressbook
/* empty for address books don't supporting groups */ /* empty for address books don't supporting groups */
return array(); return array();
} }
/**
* Utility function to return all values of a certain data column
* either as flat list or grouped by subtype
*
* @param string Col name
* @param array Record data array as used for saving
* @param boolean True to return one array with all values, False for hash array with values grouped by type
* @return array List of column values
*/
function get_col_values($col, $data, $flat = false)
{
$out = array();
foreach ($data as $c => $values) {
if (strpos($c, $col) === 0) {
if ($flat) {
$out = array_merge($out, (array)$values);
}
else {
list($f, $type) = explode(':', $c);
$out[$type] = array_merge((array)$out[$type], (array)$values);
}
}
}
return $out;
}
} }

@ -68,6 +68,7 @@ class rcube_browser
$this->dom = ($this->mz || $this->safari || ($this->ie && $this->ver>=5) || ($this->opera && $this->ver>=7)); $this->dom = ($this->mz || $this->safari || ($this->ie && $this->ver>=5) || ($this->opera && $this->ver>=7));
$this->pngalpha = $this->mz || $this->safari || ($this->ie && $this->ver>=5.5) || $this->pngalpha = $this->mz || $this->safari || ($this->ie && $this->ver>=5.5) ||
($this->ie && $this->ver>=5 && $this->mac) || ($this->opera && $this->ver>=7) ? true : false; ($this->ie && $this->ver>=5 && $this->mac) || ($this->opera && $this->ver>=7) ? true : false;
$this->imgdata = !$this->ie;
} }
} }

@ -47,13 +47,17 @@ class rcube_contacts extends rcube_addressbook
private $table_cols = array('name', 'email', 'firstname', 'surname', 'vcard'); private $table_cols = array('name', 'email', 'firstname', 'surname', 'vcard');
// public properties // public properties
var $primary_key = 'contact_id'; public $primary_key = 'contact_id';
var $readonly = false; public $readonly = false;
var $groups = true; public $groups = true;
var $list_page = 1; public $list_page = 1;
var $page_size = 10; public $page_size = 10;
var $group_id = 0; public $group_id = 0;
var $ready = false; public $ready = false;
public $coltypes = array('name', 'firstname', 'surname', 'middlename', 'prefix', 'suffix', 'nickname',
'jobtitle', 'organization', 'department', 'assistant', 'manager',
'gender', 'maidenname', 'spouse', 'email', 'phone', 'address',
'birthday', 'anniversary', 'website', 'im', 'notes', 'photo');
/** /**
@ -152,7 +156,7 @@ class rcube_contacts extends rcube_addressbook
/** /**
* List the current set of contact records * List the current set of contact records
* *
* @param array List of cols to show * @param array List of cols to show, Null means all
* @param int Only return this number of records, use negative values for tail * @param int Only return this number of records, use negative values for tail
* @param boolean True to skip the count query (select only) * @param boolean True to skip the count query (select only)
* @return array Indexed list of contact records, each a hash array * @return array Indexed list of contact records, each a hash array
@ -187,11 +191,21 @@ class rcube_contacts extends rcube_addressbook
$this->user_id, $this->user_id,
$this->group_id); $this->group_id);
// determine whether we have to parse the vcard or if only db cols are requested
$read_vcard = !$cols || count(array_intersect($cols, $this->table_cols)) < count($cols);
while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) { while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) {
$sql_arr['ID'] = $sql_arr[$this->primary_key]; $sql_arr['ID'] = $sql_arr[$this->primary_key];
if ($read_vcard)
$sql_arr = $this->convert_db_data($sql_arr);
else
$sql_arr['email'] = preg_split('/,\s*/', $sql_arr['email']);
// make sure we have a name to display // make sure we have a name to display
if (empty($sql_arr['name'])) if (empty($sql_arr['name']))
$sql_arr['name'] = $sql_arr['email']; $sql_arr['name'] = $sql_arr['email'][0];
$this->result->add($sql_arr); $this->result->add($sql_arr);
} }
@ -222,7 +236,7 @@ class rcube_contacts extends rcube_addressbook
* @param boolean True if results are requested, False if count only * @param boolean True if results are requested, False if count only
* @param boolean True to skip the count query (select only) * @param boolean True to skip the count query (select only)
* @param array List of fields that cannot be empty * @param array List of fields that cannot be empty
* @return Indexed list of contact records and 'count' value * @return object rcube_result_set Contact records and 'count' value
*/ */
function search($fields, $value, $strict=false, $select=true, $nocount=false, $required=array()) function search($fields, $value, $strict=false, $select=true, $nocount=false, $required=array())
{ {
@ -345,12 +359,12 @@ class rcube_contacts extends rcube_addressbook
); );
if ($sql_arr = $this->db->fetch_assoc()) { if ($sql_arr = $this->db->fetch_assoc()) {
$sql_arr['ID'] = $sql_arr[$this->primary_key]; $record = $this->convert_db_data($sql_arr);
$this->result = new rcube_result_set(1); $this->result = new rcube_result_set(1);
$this->result->add($sql_arr); $this->result->add($record);
} }
return $assoc && $sql_arr ? $sql_arr : $this->result; return $assoc && $record ? $record : $this->result;
} }
@ -389,21 +403,29 @@ class rcube_contacts extends rcube_addressbook
*/ */
function insert($save_data, $check=false) function insert($save_data, $check=false)
{ {
if (is_object($save_data) && is_a($save_data, rcube_result_set)) if (!is_array($save_data))
return $this->insert_recset($save_data, $check); return false;
$insert_id = $existing = false; $insert_id = $existing = false;
if ($check) if ($check) {
$existing = $this->search('email', $save_data['email'], true, false); foreach ($save_data as $col => $values) {
if (strpos($col, 'email') === 0) {
foreach ((array)$values as $email) {
if ($existing = $this->search('email', $email, true, false))
break 2;
}
}
}
}
$save_data = $this->convert_save_data($save_data);
$a_insert_cols = $a_insert_values = array(); $a_insert_cols = $a_insert_values = array();
foreach ($this->table_cols as $col) foreach ($save_data as $col => $value) {
if (isset($save_data[$col])) { $a_insert_cols[] = $this->db->quoteIdentifier($col);
$a_insert_cols[] = $this->db->quoteIdentifier($col); $a_insert_values[] = $this->db->quote($value);
$a_insert_values[] = $this->db->quote($save_data[$col]); }
}
if (!$existing->count && !empty($a_insert_cols)) { if (!$existing->count && !empty($a_insert_cols)) {
$this->db->query( $this->db->query(
@ -425,20 +447,6 @@ class rcube_contacts extends rcube_addressbook
} }
/**
* Insert new contacts for each row in set
*/
function insert_recset($result, $check=false)
{
$ids = array();
while ($row = $result->next()) {
if ($insert = $this->insert($row, $check))
$ids[] = $insert;
}
return $ids;
}
/** /**
* Update a specific contact record * Update a specific contact record
* *
@ -450,11 +458,12 @@ class rcube_contacts extends rcube_addressbook
{ {
$updated = false; $updated = false;
$write_sql = array(); $write_sql = array();
$record = $this->get_record($id, true);
$save_cols = $this->convert_save_data($save_cols, $record);
foreach ($this->table_cols as $col) foreach ($save_cols as $col => $value) {
if (isset($save_cols[$col])) $write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col), $this->db->quote($value));
$write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col), }
$this->db->quote($save_cols[$col]));
if (!empty($write_sql)) { if (!empty($write_sql)) {
$this->db->query( $this->db->query(
@ -468,10 +477,61 @@ class rcube_contacts extends rcube_addressbook
); );
$updated = $this->db->affected_rows(); $updated = $this->db->affected_rows();
$this->result = null; // clear current result (from get_record())
} }
return $updated; return $updated;
} }
private function convert_db_data($sql_arr)
{
$record = array();
$record['ID'] = $sql_arr[$this->primary_key];
if ($sql_arr['vcard']) {
unset($sql_arr['email']);
$vcard = new rcube_vcard($sql_arr['vcard']);
$record += $vcard->get_assoc() + $sql_arr;
}
else {
$record += $sql_arr;
$record['email'] = preg_split('/,\s*/', $record['email']);
}
return $record;
}
private function convert_save_data($save_data, $record = array())
{
$out = array();
// 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);
foreach ((array)$values as $value) {
if (isset($value))
$vcard->set($field, $value, $section);
}
}
$out['vcard'] = $vcard->export();
foreach ($this->table_cols as $col) {
$key = $col;
if (!isset($save_data[$key]))
$key .= ':home';
if (isset($save_data[$key]))
$out[$col] = is_array($save_data[$key]) ? join(',', $save_data[$key]) : $save_data[$key];
}
// save all e-mails in database column
$out['email'] = join(", ", $vcard->email);
return $out;
}
/** /**

@ -26,23 +26,24 @@
*/ */
class rcube_ldap extends rcube_addressbook class rcube_ldap extends rcube_addressbook
{ {
var $conn; protected $conn;
var $prop = array(); protected $prop = array();
var $fieldmap = array(); protected $fieldmap = array();
var $filter = ''; protected $filter = '';
var $result = null; protected $result = null;
var $ldap_result = null; protected $ldap_result = null;
var $sort_col = ''; protected $sort_col = '';
var $mail_domain = ''; protected $mail_domain = '';
var $debug = false; protected $debug = false;
/** public properties */ /** public properties */
var $primary_key = 'ID'; public $primary_key = 'ID';
var $readonly = true; public $readonly = true;
var $list_page = 1; public $list_page = 1;
var $page_size = 10; public $page_size = 10;
var $ready = false; public $ready = false;
public $coltypes = array();
/** /**
@ -57,9 +58,37 @@ class rcube_ldap extends rcube_addressbook
{ {
$this->prop = $p; $this->prop = $p;
foreach ($p as $prop => $value) // fieldmap property is given
if (preg_match('/^(.+)_field$/', $prop, $matches)) if (is_array($p['fieldmap'])) {
$this->fieldmap[$matches[1]] = $this->_attr_name(strtolower($value)); foreach ($p['fieldmap'] as $rf => $lf)
$this->fieldmap[$rf] = $this->_attr_name(strtolower($lf));
}
else {
// read deprecated *_field properties to remain backwards compatible
foreach ($p as $prop => $value)
if (preg_match('/^(.+)_field$/', $prop, $matches))
$this->fieldmap[$matches[1]] = $this->_attr_name(strtolower($value));
}
// use fieldmap to advertise supported coltypes to the application
foreach ($this->fieldmap as $col => $lf) {
list($col, $type) = explode(':', $col);
if (!is_array($this->coltypes[$col])) {
$subtypes = $type ? array($type) : null;
$this->coltypes[$col] = array('limit' => 2, 'subtypes' => $subtypes);
}
else if ($type) {
$this->coltypes[$col]['subtypes'][] = $type;
$this->coltypes[$col]['limit']++;
}
if ($type && !$this->fieldmap[$col])
$this->fieldmap[$col] = $lf;
}
if ($this->fieldmap['street'] && $this->fieldmap['locality'])
$this->coltypes['address'] = array('limit' => 1);
else if ($this->coltypes['address'])
$this->coltypes['address'] = array('type' => 'textarea', 'childs' => null, 'limit' => 1, 'size' => 40);
// make sure 'required_fields' is an array // make sure 'required_fields' is an array
if (!is_array($this->prop['required_fields'])) if (!is_array($this->prop['required_fields']))
@ -455,7 +484,7 @@ class rcube_ldap extends rcube_addressbook
if ($entry && ($rec = ldap_get_attributes($this->conn, $entry))) if ($entry && ($rec = ldap_get_attributes($this->conn, $entry)))
{ {
$this->_debug("S: OK"); $this->_debug("S: OK"/* . print_r($rec, true)*/);
$rec = array_change_key_case($rec, CASE_LOWER); $rec = array_change_key_case($rec, CASE_LOWER);
@ -482,8 +511,10 @@ class rcube_ldap extends rcube_addressbook
// Map out the column names to their LDAP ones to build the new entry. // Map out the column names to their LDAP ones to build the new entry.
$newentry = array(); $newentry = array();
$newentry['objectClass'] = $this->prop['LDAP_Object_Classes']; $newentry['objectClass'] = $this->prop['LDAP_Object_Classes'];
foreach ($save_cols as $col => $val) { foreach ($this->fieldmap as $col => $fld) {
$fld = $this->_map_field($col); $val = $save_cols[$col];
if (is_array($val))
$val = array_filter($val); // remove empty entries
if ($fld && $val) { if ($fld && $val) {
// The field does exist, add it to the entry. // The field does exist, add it to the entry.
$newentry[$fld] = $val; $newentry[$fld] = $val;
@ -491,23 +522,29 @@ class rcube_ldap extends rcube_addressbook
} // end foreach } // end foreach
// Verify that the required fields are set. // Verify that the required fields are set.
// We know that the email address is required as a default of rcube, so
// we will default its value into any unfilled required fields.
foreach ($this->prop['required_fields'] as $fld) { foreach ($this->prop['required_fields'] as $fld) {
$missing = null;
if (!isset($newentry[$fld])) { if (!isset($newentry[$fld])) {
$newentry[$fld] = $newentry[$this->_map_field('email')]; $missing[] = $fld;
} // end if }
} // end foreach }
// abort process if requiered fields are missing
// TODO: generate message saying which fields are missing
if ($missing) {
$this->set_error(self::ERROR_INCOMPLETE, 'formincomplete');
return false;
}
// Build the new entries DN. // Build the new entries DN.
$dn = $this->prop['LDAP_rdn'].'='.rcube_ldap::quote_string($newentry[$this->prop['LDAP_rdn']], true) $dn = $this->prop['LDAP_rdn'].'='.rcube_ldap::quote_string($newentry[$this->prop['LDAP_rdn']], true).','.$this->prop['base_dn'];
.','.$this->prop['base_dn'];
$this->_debug("C: Add [dn: $dn]: ".print_r($newentry, true)); $this->_debug("C: Add [dn: $dn]: ".print_r($newentry, true));
$res = ldap_add($this->conn, $dn, $newentry); $res = ldap_add($this->conn, $dn, $newentry);
if ($res === FALSE) { if ($res === FALSE) {
$this->_debug("S: ".ldap_error($this->conn)); $this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false; return false;
} // end if } // end if
@ -533,8 +570,8 @@ class rcube_ldap extends rcube_addressbook
$newdata = array(); $newdata = array();
$replacedata = array(); $replacedata = array();
$deletedata = array(); $deletedata = array();
foreach ($save_cols as $col => $val) { foreach ($this->fieldmap as $col => $fld) {
$fld = $this->_map_field($col); $val = $save_cols[$col];
if ($fld) { if ($fld) {
// The field does exist compare it to the ldap record. // The field does exist compare it to the ldap record.
if ($record[$col] != $val) { if ($record[$col] != $val) {
@ -566,6 +603,7 @@ class rcube_ldap extends rcube_addressbook
$this->_debug("C: Delete [dn: $dn]: ".print_r($deletedata, true)); $this->_debug("C: Delete [dn: $dn]: ".print_r($deletedata, true));
if (!ldap_mod_del($this->conn, $dn, $deletedata)) { if (!ldap_mod_del($this->conn, $dn, $deletedata)) {
$this->_debug("S: ".ldap_error($this->conn)); $this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false; return false;
} }
$this->_debug("S: OK"); $this->_debug("S: OK");
@ -575,11 +613,11 @@ class rcube_ldap extends rcube_addressbook
// Handle RDN change // Handle RDN change
if ($replacedata[$this->prop['LDAP_rdn']]) { if ($replacedata[$this->prop['LDAP_rdn']]) {
$newdn = $this->prop['LDAP_rdn'].'=' $newdn = $this->prop['LDAP_rdn'].'='
.rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true) .rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true)
.','.$this->prop['base_dn']; .','.$this->prop['base_dn'];
if ($dn != $newdn) { if ($dn != $newdn) {
$newrdn = $this->prop['LDAP_rdn'].'=' $newrdn = $this->prop['LDAP_rdn'].'='
.rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true); .rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true);
unset($replacedata[$this->prop['LDAP_rdn']]); unset($replacedata[$this->prop['LDAP_rdn']]);
} }
} }
@ -589,7 +627,7 @@ class rcube_ldap extends rcube_addressbook
if (!ldap_mod_replace($this->conn, $dn, $replacedata)) { if (!ldap_mod_replace($this->conn, $dn, $replacedata)) {
$this->_debug("S: ".ldap_error($this->conn)); $this->_debug("S: ".ldap_error($this->conn));
return false; return false;
} }
$this->_debug("S: OK"); $this->_debug("S: OK");
} // end if } // end if
} // end if } // end if
@ -599,6 +637,7 @@ class rcube_ldap extends rcube_addressbook
$this->_debug("C: Add [dn: $dn]: ".print_r($newdata, true)); $this->_debug("C: Add [dn: $dn]: ".print_r($newdata, true));
if (!ldap_mod_add($this->conn, $dn, $newdata)) { if (!ldap_mod_add($this->conn, $dn, $newdata)) {
$this->_debug("S: ".ldap_error($this->conn)); $this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false; return false;
} }
$this->_debug("S: OK"); $this->_debug("S: OK");
@ -638,6 +677,7 @@ class rcube_ldap extends rcube_addressbook
$res = ldap_delete($this->conn, $dn); $res = ldap_delete($this->conn, $dn);
if ($res === FALSE) { if ($res === FALSE) {
$this->_debug("S: ".ldap_error($this->conn)); $this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false; return false;
} // end if } // end if
$this->_debug("S: OK"); $this->_debug("S: OK");
@ -679,8 +719,6 @@ class rcube_ldap extends rcube_addressbook
*/ */
private function _ldap2result($rec) private function _ldap2result($rec)
{ {
global $RCMAIL;
$out = array(); $out = array();
if ($rec['dn']) if ($rec['dn'])
@ -688,11 +726,17 @@ class rcube_ldap extends rcube_addressbook
foreach ($this->fieldmap as $rf => $lf) foreach ($this->fieldmap as $rf => $lf)
{ {
if ($rec[$lf]['count']) { for ($i=0; $i < $rec[$lf]['count']; $i++) {
if ($rf == 'email' && $this->mail_domain && !strpos($rec[$lf][0], '@')) if (!($value = $rec[$lf][$i]))
$out[$rf] = sprintf('%s@%s', $rec[$lf][0], $this->mail_domain); continue;
if ($rf == 'email' && $this->mail_domain && !strpos($value, '@'))
$out[$rf][] = sprintf('%s@%s', $value, $this->mail_domain);
else if (in_array($rf, array('street','zipcode','locality','country','region')))
$out['address'][$i][$rf] = $value;
else if ($rec[$lf]['count'] > 1)
$out[$rf][] = $value;
else else
$out[$rf] = $rec[$lf][0]; $out[$rf] = $value;
} }
} }
@ -741,6 +785,10 @@ class rcube_ldap extends rcube_addressbook
*/ */
function quote_string($str, $dn=false) function quote_string($str, $dn=false)
{ {
// take firt entry if array given
if (is_array($str))
$str = reset($str);
if ($dn) if ($dn)
$replace = array(','=>'\2c', '='=>'\3d', '+'=>'\2b', '<'=>'\3c', $replace = array(','=>'\2c', '='=>'\3d', '+'=>'\2b', '<'=>'\3c',
'>'=>'\3e', ';'=>'\3b', '\\'=>'\5c', '"'=>'\22', '#'=>'\23'); '>'=>'\3e', ';'=>'\3b', '\\'=>'\5c', '"'=>'\22', '#'=>'\23');

@ -83,7 +83,20 @@ abstract class rcube_plugin
* Initialization method, needs to be implemented by the plugin itself * Initialization method, needs to be implemented by the plugin itself
*/ */
abstract function init(); abstract function init();
/**
* Attempt to load the given plugin which is required for the current plugin
*
* @param string Plugin name
* @return boolean True on success, false on failure
*/
public function require_plugin($plugin_name)
{
return $this->api->load_plugin($plugin_name);
}
/** /**
* Load local config file from plugins directory. * Load local config file from plugins directory.
* The loaded values are patched over the global configuration. * The loaded values are patched over the global configuration.

@ -109,42 +109,9 @@ class rcube_plugin_api
$this->output = $rcmail->output; $this->output = $rcmail->output;
$this->config = $rcmail->config; $this->config = $rcmail->config;
$plugins_dir = dir($this->dir);
$plugins_dir = unslashify($plugins_dir->path);
$plugins_enabled = (array)$rcmail->config->get('plugins', array()); $plugins_enabled = (array)$rcmail->config->get('plugins', array());
foreach ($plugins_enabled as $plugin_name) { foreach ($plugins_enabled as $plugin_name) {
$fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; $this->load_plugin($plugin_name);
if (file_exists($fn)) {
include($fn);
// instantiate class if exists
if (class_exists($plugin_name, false)) {
$plugin = new $plugin_name($this);
// check inheritance...
if (is_subclass_of($plugin, 'rcube_plugin')) {
// ... task, request type and framed mode
if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $rcmail->task))
&& (!$plugin->noajax || is_a($this->output, 'rcube_template'))
&& (!$plugin->noframe || empty($_REQUEST['_framed']))
) {
$plugin->init();
$this->plugins[] = $plugin;
}
}
}
else {
raise_error(array('code' => 520, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "No plugin class $plugin_name found in $fn"), true, false);
}
}
else {
raise_error(array('code' => 520, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to load plugin file $fn"), true, false);
}
} }
// check existance of all required core plugins // check existance of all required core plugins
@ -158,31 +125,14 @@ class rcube_plugin_api
} }
// load required core plugin if no derivate was found // load required core plugin if no derivate was found
if (!$loaded) { if (!$loaded)
$fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; $loaded = $this->load_plugin($plugin_name);
if (file_exists($fn)) {
include_once($fn);
if (class_exists($plugin_name, false)) {
$plugin = new $plugin_name($this);
// check inheritance
if (is_subclass_of($plugin, 'rcube_plugin')) {
if (!$plugin->task || preg_match('/('.$plugin->task.')/i', $rcmail->task)) {
$plugin->init();
$this->plugins[] = $plugin;
}
$loaded = true;
}
}
}
}
// trigger fatal error if still not loaded // trigger fatal error if still not loaded
if (!$loaded) { if (!$loaded) {
raise_error(array('code' => 520, 'type' => 'php', raise_error(array('code' => 520, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__, 'file' => __FILE__, 'line' => __LINE__,
'message' => "Requried plugin $plugin_name was not loaded"), true, true); 'message' => "Requried plugin $plugin_name was not loaded"), true, true);
} }
} }
@ -191,6 +141,64 @@ class rcube_plugin_api
// maybe also register a shudown function which triggers shutdown functions of all plugin objects // maybe also register a shudown function which triggers shutdown functions of all plugin objects
} }
/**
* Load the specified plugin
*
* @param string Plugin name
* @return boolean True on success, false if not loaded or failure
*/
public function load_plugin($plugin_name)
{
static $plugins_dir;
$rcmail = rcmail::get_instance();
if (!$plugins_dir) {
$dir = dir($this->dir);
$plugins_dir = unslashify($dir->path);
}
// plugin already loaded
if ($this->plugins[$plugin_name] || class_exists($plugin_name, false))
return true;
$fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
if (file_exists($fn)) {
include($fn);
// instantiate class if exists
if (class_exists($plugin_name, false)) {
$plugin = new $plugin_name($this);
// check inheritance...
if (is_subclass_of($plugin, 'rcube_plugin')) {
// ... task, request type and framed mode
if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $rcmail->task)) /*
&& (!$plugin->noajax || is_a($rcmail->output, 'rcube_template'))
&& (!$plugin->noframe || empty($_REQUEST['_framed']))*/
) {
$plugin->init();
$this->plugins[$plugin_name] = $plugin;
}
return true;
}
}
else {
raise_error(array('code' => 520, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "No plugin class $plugin_name found in $fn"), true, false);
}
}
else {
raise_error(array('code' => 520, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to load plugin file $fn"), true, false);
}
return false;
}
/** /**

@ -485,6 +485,25 @@ function rc_mime_content_type($path, $name, $failover = 'application/octet-strea
return $mime_type; return $mime_type;
} }
/**
* Detect image type of the given binary data by checking magic numbers
*
* @param string Binary file content
* @return string Detected mime-type or jpeg as fallback
*/
function rc_image_content_type($data)
{
$type = 'jpeg';
if (preg_match('/^\x89\x50\x4E\x47/', $data)) $type = 'png';
else if (preg_match('/^\x47\x49\x46\x38/', $data)) $type = 'gif';
else if (preg_match('/^\x00\x00\x01\x00/', $data)) $type = 'ico';
// else if (preg_match('/^\xFF\xD8\xFF\xE0/', $data)) $type = 'jpeg';
return 'image/' . $type;
}
/** /**
* A method to guess encoding of a string. * A method to guess encoding of a string.
* *

@ -996,8 +996,11 @@ class rcube_template extends rcube_html_page
$attrib['action'] = './'; $attrib['action'] = './';
// we already have a <form> tag // we already have a <form> tag
if ($attrib['form']) if ($attrib['form']) {
if ($this->framed || !empty($_REQUEST['_framed']))
$hidden->add(array('name' => '_framed', 'value' => '1'));
return $hidden->show() . $content; return $hidden->show() . $content;
}
else else
return $this->form_tag($attrib, $hidden->show() . $content); return $this->form_tag($attrib, $hidden->show() . $content);
} }

@ -5,7 +5,7 @@
| program/include/rcube_vcard.php | | program/include/rcube_vcard.php |
| | | |
| This file is part of the Roundcube Webmail client | | This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2009, The Roundcube Dev Team | | Copyright (C) 2008-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL | | Licensed under the GNU GPL |
| | | |
| PURPOSE: | | PURPOSE: |
@ -33,6 +33,24 @@ class rcube_vcard
'FN' => array(), 'FN' => array(),
'N' => array(array('','','','','')), 'N' => array(array('','','','','')),
); );
private $fieldmap = array(
'phone' => 'TEL',
'birthday' => 'BDAY',
'website' => 'URL',
'notes' => 'NOTE',
'email' => 'EMAIL',
'address' => 'ADR',
'gender' => 'X-GENDER',
'maidenname' => 'X-MAIDENNAME',
'anniversary' => 'X-ANNIVERSARY',
'assistant' => 'X-ASSISTANT',
'manager' => 'X-MANAGER',
'spouse' => 'X-SPOUSE',
);
private $typemap = array('iPhone' => 'mobile', 'CELL' => 'mobile');
private $phonetypemap = array('HOME1' => 'HOME', 'BUSINESS1' => 'WORK', 'BUSINESS2' => 'WORK2', 'WORKFAX' => 'BUSINESSFAX');
private $addresstypemap = array('BUSINESS' => 'WORK');
private $immap = array('X-JABBER' => 'jabber', 'X-ICQ' => 'icq', 'X-MSN' => 'msn', 'X-AIM' => 'aim', 'X-YAHOO' => 'yahoo', 'X-SKYPE' => 'skype', 'X-SKYPE-USERNAME' => 'skype');
public $business = false; public $business = false;
public $displayname; public $displayname;
@ -106,6 +124,64 @@ class rcube_vcard
} }
/**
* Return vCard data as associative array to be unsed in Roundcube address books
*
* @return array Hash array with key-value pairs
*/
public function get_assoc()
{
$out = array('name' => $this->displayname);
$typemap = $this->typemap;
// copy name fields to output array
foreach (array('firstname','surname','middlename','nickname','organization') as $col)
$out[$col] = $this->$col;
$out['prefix'] = $this->raw['N'][0][3];
$out['suffix'] = $this->raw['N'][0][4];
// convert from raw vcard data into associative data for Roundcube
foreach (array_flip($this->fieldmap) as $tag => $col) {
foreach ((array)$this->raw[$tag] as $i => $raw) {
if (is_array($raw)) {
$k = -1;
$key = $col;
$subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref'))
$subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
if ($subtype)
$key .= ':' . $subtype;
// split ADR values into assoc array
if ($tag == 'ADR') {
list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw;
$out[$key][] = $value;
}
else
$out[$key][] = $raw[0];
}
else {
$out[$col][] = $raw;
}
}
}
// handle special IM fields as used by Apple
foreach ($this->immap as $tag => $type) {
foreach ((array)$this->raw[$tag] as $i => $raw) {
$out['im:'.$type][] = $raw[0];
}
}
// copy photo data
if ($this->raw['PHOTO'])
$out['photo'] = $this->raw['PHOTO'][0][0];
return $out;
}
/** /**
* Convert the data structure into a vcard 3.0 string * Convert the data structure into a vcard 3.0 string
*/ */
@ -113,6 +189,28 @@ class rcube_vcard
{ {
return self::rfc2425_fold(self::vcard_encode($this->raw)); return self::rfc2425_fold(self::vcard_encode($this->raw));
} }
/**
* Clear the given fields in the loaded vcard data
*
* @param array List of field names to be reset
*/
public function reset($fields = null)
{
if (!$fields)
$fields = array_merge(array_values($this->fieldmap), array_keys($this->immap), array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY'));
foreach ($fields as $f)
unset($this->raw[$f]);
if (!$this->raw['N'])
$this->raw['N'] = array(array('','','','',''));
if (!$this->raw['FN'])
$this->raw['FN'] = array();
$this->email = array();
}
/** /**
@ -120,24 +218,40 @@ class rcube_vcard
* *
* @param string Field name * @param string Field name
* @param string Field value * @param string Field value
* @param string Section name * @param string Type/section name
*/ */
public function set($field, $value, $section = 'HOME') public function set($field, $value, $type = 'HOME')
{ {
$field = strtolower($field);
$type = strtoupper($type);
$typemap = array_flip($this->typemap);
switch ($field) { switch ($field) {
case 'name': case 'name':
case 'displayname': case 'displayname':
$this->raw['FN'][0][0] = $value; $this->raw['FN'][0][0] = $value;
break; break;
case 'surname':
$this->raw['N'][0][0] = $value;
break;
case 'firstname': case 'firstname':
$this->raw['N'][0][1] = $value; $this->raw['N'][0][1] = $value;
break; break;
case 'surname': case 'middlename':
$this->raw['N'][0][0] = $value; $this->raw['N'][0][2] = $value;
break; break;
case 'prefix':
$this->raw['N'][0][3] = $value;
break;
case 'suffix':
$this->raw['N'][0][4] = $value;
break;
case 'nickname': case 'nickname':
$this->raw['NICKNAME'][0][0] = $value; $this->raw['NICKNAME'][0][0] = $value;
break; break;
@ -146,13 +260,47 @@ class rcube_vcard
$this->raw['ORG'][0][0] = $value; $this->raw['ORG'][0][0] = $value;
break; break;
case 'photo':
$encoded = !preg_match('![^a-z0-9/=+-]!i', $value);
$this->raw['PHOTO'][0] = array(0 => $encoded ? $value : base64_encode($value), 'BASE64' => true);
break;
case 'email': case 'email':
$index = $this->get_type_index('EMAIL', $section); $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type)));
if (!is_array($this->raw['EMAIL'][$index])) { $this->email[] = $value;
$this->raw['EMAIL'][$index] = array(0 => $value, 'type' => array('INTERNET', $section, 'pref')); break;
}
else { case 'im':
$this->raw['EMAIL'][$index][0] = $value; // save IM subtypes into extension fields
$typemap = array_flip($this->immap);
if ($field = $typemap[strtolower($type)])
$this->raw[$field][] = array(0 => $value);
break;
case 'birthday':
if ($val = @strtotime($value))
$this->raw['BDAY'][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
break;
case 'address':
if ($this->addresstypemap[$type])
$type = $this->addresstypemap[$type];
$value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']);
// fall through if not empty
if (!strlen(join('', $value)))
break;
default:
if ($field == 'phone' && $this->phonetypemap[$type])
$type = $this->phonetypemap[$type];
if (($tag = $this->fieldmap[$field]) && (is_array($value) || strlen($value))) {
$index = count($this->raw[$tag]);
$this->raw[$tag][$index] = (array)$value;
if ($type)
$this->raw[$tag][$index]['type'] = array(($typemap[$type] ? $typemap[$type] : $type));
} }
break; break;
} }

@ -319,7 +319,20 @@ function rcube_webmail()
if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform) { if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform) {
this.enable_command('save', true); this.enable_command('save', true);
$("input[type='text']").first().select(); this.enable_command('upload-photo', this.env.coltypes.photo ? true : false);
this.enable_command('delete-photo', this.env.coltypes.photo && this.env.action == 'edit');
for (var col in this.env.coltypes)
this.init_edit_field(col, null);
$('.contactfieldgroup .row a.deletebutton').click(function(){ ref.delete_edit_field(this); return false });
$('select.addfieldmenu').change(function(e){
ref.insert_edit_field($(this).val(), $(this).attr('rel'), this);
this.selectedIndex = 0;
});
$("input[type='text']").first().focus();
} }
else if (this.gui_objects.qsearchbox) { else if (this.gui_objects.qsearchbox) {
this.enable_command('search', 'reset-search', 'moveto', true); this.enable_command('search', 'reset-search', 'moveto', true);
@ -639,6 +652,9 @@ function rcube_webmail()
input_email.focus(); input_email.focus();
break; break;
} }
// clear empty input fields
$('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
} }
this.gui_objects.editform.submit(); this.gui_objects.editform.submit();
@ -996,13 +1012,17 @@ function rcube_webmail()
case 'export': case 'export':
if (this.contact_list.rowcount > 0) { if (this.contact_list.rowcount > 0) {
var add_url = (this.env.source ? '_source='+urlencode(this.env.source)+'&' : ''); this.goto_url('export', { _source:this.env.source, _gid:this.env.group, _search:this.env.search_request });
if (this.env.search_request)
add_url += '_search='+this.env.search_request;
this.goto_url('export', add_url);
} }
break; break;
case 'upload-photo':
this.upload_contact_photo(props);
break;
case 'delete-photo':
this.replace_contact_photo('-del-');
break;
// user settings commands // user settings commands
case 'preferences': case 'preferences':
@ -1158,7 +1178,7 @@ function rcube_webmail()
this.is_framed = function() this.is_framed = function()
{ {
return (this.env.framed && parent.rcmail); return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command);
}; };
@ -3177,27 +3197,7 @@ function rcube_webmail()
// create hidden iframe and post upload form // create hidden iframe and post upload form
if (send) { if (send) {
var ts = new Date().getTime(); this.async_upload_form(form, 'upload', function(e) {
var frame_name = 'rcmupload'+ts;
// have to do it this way for IE
// otherwise the form will be posted to a new window
if (document.all) {
var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
document.body.insertAdjacentHTML('BeforeEnd',html);
}
else { // for standards-compilant browsers
var frame = document.createElement('iframe');
frame.name = frame_name;
frame.style.border = 'none';
frame.style.width = 0;
frame.style.height = 0;
frame.style.visibility = 'hidden';
document.body.appendChild(frame);
}
// handle upload errors, parsing iframe content in onload
$(frame_name).bind('load', {ts:ts}, function(e) {
var d, content = ''; var d, content = '';
try { try {
if (this.contentDocument) { if (this.contentDocument) {
@ -3218,11 +3218,6 @@ function rcube_webmail()
rcmail.env.uploadframe = e.data.ts; rcmail.env.uploadframe = e.data.ts;
}); });
form.target = frame_name;
form.action = this.env.comm_path+'&_action=upload&_uploadid='+ts;
form.setAttribute('enctype', 'multipart/form-data');
form.submit();
// display upload indicator and cancel button // display upload indicator and cancel button
var content = this.get_label('uploading'); var content = this.get_label('uploading');
if (this.env.loadingicon) if (this.env.loadingicon)
@ -3979,6 +3974,165 @@ function rcube_webmail()
}; };
this.init_edit_field = function(col, elem)
{
if (!elem)
elem = $('.ff_' + col);
elem.focus(function(){ ref.focus_textfield(this); })
.blur(function(){ ref.blur_textfield(this); })
.each(function(){ this._placeholder = ref.env.coltypes[col].label; ref.blur_textfield(this); });
};
this.insert_edit_field = function(col, section, menu)
{
// just make pre-defined input field visible
var elem = $('#ff_'+col);
if (elem.length) {
elem.show().focus();
$(menu).children('option[value="'+col+'"]').attr('disabled', true);
}
else {
var lastelem = $('.ff_'+col),
appendcontainer = $('#contactsection'+section+' .contactcontroller'+col);
if (!appendcontainer.length)
appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col).insertAfter($('#contactsection'+section+' .contactfieldgroup').last());
if (appendcontainer.length && appendcontainer.get(0).nodeName == 'FIELDSET') {
var input, colprop = this.env.coltypes[col],
row = $('<div>').addClass('row'),
cell = $('<div>').addClass('contactfieldcontent data'),
label = $('<div>').addClass('contactfieldlabel label');
if (colprop.subtypes_select)
label.html(colprop.subtypes_select);
else
label.html(colprop.label);
var name_suffix = colprop.limit != 1 ? '[]' : '';
if (colprop.type == 'text' || colprop.type == 'date') {
input = $('<input>')
.addClass('ff_'+col)
.attr('type', 'text')
.attr('name', '_'+col+name_suffix)
.attr('size', colprop.size)
.appendTo(cell);
this.init_edit_field(col, input);
}
else if (colprop.type == 'composite') {
var childcol, cp, first;
for (var childcol in colprop.childs) {
cp = colprop.childs[childcol];
input = $('<input>')
.addClass('ff_'+childcol)
.attr('type', 'text')
.attr('name', '_'+childcol+name_suffix)
.attr('size', cp.size)
.appendTo(cell);
cell.append(" ");
this.init_edit_field(childcol, input);
if (!first) first = input;
}
input = first; // set focus to the first of this composite fields
}
else if (colprop.type == 'select') {
input = $('<select>')
.addClass('ff_'+col)
.attr('name', '_'+col+name_suffix)
.appendTo(cell);
var options = input.attr('options');
options[options.length] = new Option('---', '');
if (colprop.options)
$.each(colprop.options, function(i, val){ options[options.length] = new Option(val, i); });
}
if (input) {
var delbutton = $('<a href="#del"></a>')
.addClass('contactfieldbutton deletebutton')
.attr('title', this.get_label('delete'))
.attr('rel', col)
.html(this.env.delbutton)
.click(function(){ ref.delete_edit_field(this); return false })
.appendTo(cell);
row.append(label).append(cell).appendTo(appendcontainer.show());
input.first().focus();
// disable option if limit reached
if (!colprop.count) colprop.count = 0;
if (++colprop.count == colprop.limit && colprop.limit)
$(menu).children('option[value="'+col+'"]').attr('disabled', true);
}
}
}
};
this.delete_edit_field = function(elem)
{
var col = $(elem).attr('rel'),
colprop = this.env.coltypes[col],
fieldset = $(elem).parents('fieldset.contactfieldgroup'),
addmenu = fieldset.parent().find('select.addfieldmenu');
// just clear input but don't hide the last field
if (--colprop.count <= 0 && colprop.visible)
$(elem).parent().children('input').val('').blur();
else {
$(elem).parents('div.row').remove();
// hide entire fieldset if no more rows
if (!fieldset.children('div.row').length)
fieldset.hide();
}
// enable option in add-field selector or insert it if necessary
if (addmenu.length) {
var option = addmenu.children('option[value="'+col+'"]');
if (option.length)
option.attr('disabled', false);
else
option = $('<option>').attr('value', col).html(colprop.label).appendTo(addmenu);
addmenu.show();
}
};
this.upload_contact_photo = function(form)
{
if (form && form.elements._photo.value) {
this.async_upload_form(form, 'upload-photo', function(e) {
rcmail.set_busy(false, null, rcmail.photo_upload_id);
});
// display upload indicator
this.photo_upload_id = this.set_busy(true, 'uploading');
}
};
this.replace_contact_photo = function(id)
{
$('#ff_photo').val(id);
var buttons = this.buttons['upload-photo'];
for (var n=0; n < buttons.length; n++)
$('#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto'));
var img_src = id == '-del-' ? this.env.photo_placeholder :
this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + this.env.cid + '&_photo=' + id;
$(this.gui_objects.contactphoto).children('img').attr('src', img_src);
this.enable_command('delete-photo', id != '-del-');
};
this.photo_upload_end = function()
{
this.set_busy(false, null, this.photo_upload_id);
delete this.photo_upload_id;
};
/*********************************************************/ /*********************************************************/
/********* user settings methods *********/ /********* user settings methods *********/
/*********************************************************/ /*********************************************************/
@ -4505,6 +4659,23 @@ function rcube_webmail()
} }
}; };
this.focus_textfield = function(elem)
{
elem._hasfocus = true;
var $elem = $(elem);
if ($elem.hasClass('placeholder') || $elem.val() == elem._placeholder)
$elem.val('').removeClass('placeholder').attr('spellcheck', true);
};
this.blur_textfield = function(elem)
{
elem._hasfocus = false;
var $elem = $(elem);
if (elem._placeholder && (!$elem.val() || $elem.val() == elem._placeholder))
$elem.addClass('placeholder').attr('spellcheck', false).val(elem._placeholder);
};
// write to the document/window title // write to the document/window title
this.set_pagetitle = function(title) this.set_pagetitle = function(title)
{ {
@ -4951,6 +5122,39 @@ function rcube_webmail()
/********************************************************/ /********************************************************/
/********* remote request methods *********/ /********* remote request methods *********/
/********************************************************/ /********************************************************/
// compose a valid url with the given parameters
this.url = function(action, query)
{
var querystring = typeof(query) == 'string' ? '&' + query : '';
if (typeof action != 'string')
query = action;
else if (!query || typeof(query) != 'object')
query = {};
if (action)
query._action = action;
else
query._action = this.env.action;
var base = this.env.comm_path;
// overwrite task name
if (query._action.match(/([a-z]+)\/([a-z-_]+)/)) {
query._action = RegExp.$2;
base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
}
// remove undefined values
var param = {};
for (var k in query) {
if (typeof(query[k]) != 'undefined' && query[k] !== null)
param[k] = query[k];
}
return base + '&' + $.param(param) + querystring;
};
this.redirect = function(url, lock) this.redirect = function(url, lock)
{ {
@ -4965,28 +5169,13 @@ function rcube_webmail()
this.goto_url = function(action, query, lock) this.goto_url = function(action, query, lock)
{ {
var url = this.env.comm_path, this.redirect(this.url(action, query));
querystring = query ? '&'+query : '';
// overwrite task name
if (action.match(/([a-z]+)\/([a-z-_]+)/)) {
action = RegExp.$2;
url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
}
this.redirect(url+'&_action='+action+querystring, lock);
}; };
// send a http request to the server // send a http request to the server
this.http_request = function(action, query, lock) this.http_request = function(action, query, lock)
{ {
var url = this.env.comm_path; var url = this.url(action, query);
// overwrite task name
if (action.match(/([a-z]+)\/([a-z-_]+)/)) {
action = RegExp.$2;
url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
}
// trigger plugin hook // trigger plugin hook
var result = this.triggerEvent('request'+action, query); var result = this.triggerEvent('request'+action, query);
@ -4999,7 +5188,7 @@ function rcube_webmail()
query = result; query = result;
} }
url += '&_remote=1&_action=' + action + (query ? '&' : '') + query; url += '&_remote=1';
// send request // send request
console.log('HTTP GET: ' + url); console.log('HTTP GET: ' + url);
@ -5013,15 +5202,7 @@ function rcube_webmail()
// send a http POST request to the server // send a http POST request to the server
this.http_post = function(action, postdata, lock) this.http_post = function(action, postdata, lock)
{ {
var url = this.env.comm_path; var url = this.url(action);
// overwrite task name
if (action.match(/([a-z]+)\/([a-z-_]+)/)) {
action = RegExp.$2;
url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
}
url += '&_action=' + action;
if (postdata && typeof(postdata) == 'object') { if (postdata && typeof(postdata) == 'object') {
postdata._remote = 1; postdata._remote = 1;
@ -5168,6 +5349,37 @@ function rcube_webmail()
this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error'); this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
}; };
// post the given form to a hidden iframe
this.async_upload_form = function(form, action, onload)
{
var ts = new Date().getTime();
var frame_name = 'rcmupload'+ts;
// have to do it this way for IE
// otherwise the form will be posted to a new window
if (document.all) {
var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
document.body.insertAdjacentHTML('BeforeEnd', html);
}
else { // for standards-compilant browsers
var frame = document.createElement('iframe');
frame.name = frame_name;
frame.style.border = 'none';
frame.style.width = 0;
frame.style.height = 0;
frame.style.visibility = 'hidden';
document.body.appendChild(frame);
}
// handle upload errors, parsing iframe content in onload
$(frame_name).bind('load', {ts:ts}, onload);
form.target = frame_name;
form.action = this.url(action, { _uploadid:ts });
form.setAttribute('enctype', 'multipart/form-data');
form.submit();
};
// starts interval for keep-alive/check-recent signal // starts interval for keep-alive/check-recent signal
this.start_keepalive = function() this.start_keepalive = function()
{ {

@ -243,11 +243,38 @@ $labels['yourmessage'] = 'This is a Return Receipt for your message';
$labels['receiptnote'] = 'Note: This receipt only acknowledges that the message was displayed on the recipient\'s computer. There is no guarantee that the recipient has read or understood the message contents.'; $labels['receiptnote'] = 'Note: This receipt only acknowledges that the message was displayed on the recipient\'s computer. There is no guarantee that the recipient has read or understood the message contents.';
// address boook // address boook
$labels['name'] = 'Display name'; $labels['name'] = 'Display name';
$labels['firstname'] = 'First name'; $labels['firstname'] = 'First name';
$labels['surname'] = 'Last name'; $labels['surname'] = 'Last name';
$labels['email'] = 'E-Mail'; $labels['middlename'] = 'Middle name';
$labels['nameprefix'] = 'Prefix';
$labels['namesuffix'] = 'Suffix';
$labels['nickname'] = 'Nickname';
$labels['jobtitle'] = 'Job title';
$labels['organization'] = 'Company';
$labels['department'] = 'Department';
$labels['gender'] = 'Gender';
$labels['maidenname'] = 'Maiden name';
$labels['email'] = 'E-Mail';
$labels['phone'] = 'Phone';
$labels['address'] = 'Address';
$labels['street'] = 'Street';
$labels['locality'] = 'City';
$labels['zipcode'] = 'Zip code';
$labels['region'] = 'Region';
$labels['country'] = 'Country';
$labels['birthday'] = 'Birthday';
$labels['anniversary'] = 'Anniversary';
$labels['website'] = 'Website';
$labels['instantmessenger'] = 'IM';
$labels['notes'] = 'Notes';
$labels['male'] = 'male';
$labels['female'] = 'female';
$labels['manager'] = 'Manager';
$labels['assistant'] = 'Assistant';
$labels['spouse'] = 'Spouse';
$labels['addfield'] = 'Add field...';
$labels['addcontact'] = 'Add new contact'; $labels['addcontact'] = 'Add new contact';
$labels['editcontact'] = 'Edit contact'; $labels['editcontact'] = 'Edit contact';
$labels['contacts'] = 'Contacts'; $labels['contacts'] = 'Contacts';
@ -258,6 +285,8 @@ $labels['cancel'] = 'Cancel';
$labels['save'] = 'Save'; $labels['save'] = 'Save';
$labels['delete'] = 'Delete'; $labels['delete'] = 'Delete';
$labels['rename'] = 'Rename'; $labels['rename'] = 'Rename';
$labels['addphoto'] = 'Add';
$labels['replacephoto'] = 'Replace';
$labels['newcontact'] = 'Create new contact card'; $labels['newcontact'] = 'Create new contact card';
$labels['deletecontact'] = 'Delete selected contacts'; $labels['deletecontact'] = 'Delete selected contacts';

@ -104,7 +104,7 @@ $messages['forbiddencharacter'] = 'Folder name contains a forbidden character';
$messages['selectimportfile'] = 'Please select a file to upload'; $messages['selectimportfile'] = 'Please select a file to upload';
$messages['addresswriterror'] = 'The selected address book is not writeable'; $messages['addresswriterror'] = 'The selected address book is not writeable';
$messages['contactaddedtogroup'] = 'Successfully added the contacts to this group'; $messages['contactaddedtogroup'] = 'Successfully added the contacts to this group';
$messages['contactremovedfromgroup'] = 'Successfully remove contacts from this group'; $messages['contactremovedfromgroup'] = 'Successfully removed contacts from this group';
$messages['importwait'] = 'Importing, please wait...'; $messages['importwait'] = 'Importing, please wait...';
$messages['importerror'] = 'Import failed! The uploaded file is not a valid vCard file.'; $messages['importerror'] = 'Import failed! The uploaded file is not a valid vCard file.';
$messages['importconfirm'] = '<b>Successfully imported $inserted contacts, $skipped existing entries skipped</b>:<p><em>$names</em></p>'; $messages['importconfirm'] = '<b>Successfully imported $inserted contacts, $skipped existing entries skipped</b>:<p><em>$names</em></p>';
@ -137,5 +137,6 @@ $messages['namecannotbeempty'] = 'Name cannot be empty';
$messages['nametoolong'] = 'Name is too long'; $messages['nametoolong'] = 'Name is too long';
$messages['folderupdated'] = 'Folder updated successfully'; $messages['folderupdated'] = 'Folder updated successfully';
$messages['foldercreated'] = 'Folder created successfully'; $messages['foldercreated'] = 'Folder created successfully';
$messages['invalidimageformat'] = 'Not a valid image format';
?> ?>

@ -48,7 +48,7 @@ if ($cid && preg_match('/^[a-zA-Z0-9\+\/=_-]+(,[a-zA-Z0-9\+\/=_-]+)*$/', $cid) &
'record' => $a_record, 'source' => $target, 'group' => $target_group)); 'record' => $a_record, 'source' => $target, 'group' => $target_group));
if (!$plugin['abort']) { if (!$plugin['abort']) {
if ($insert_id = $TARGET->insert($a_record, false)) { if ($insert_id = $TARGET->insert($plugin['record'], false)) {
$ids[] = $insert_id; $ids[] = $insert_id;
$success++; $success++;
} }

@ -38,6 +38,10 @@ if ($OUTPUT->ajax_call &&
// count contacts for this user // count contacts for this user
$result = $CONTACTS->count(); $result = $CONTACTS->count();
// update saved search after data changed
if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request]))
$_SESSION['search'][$search_request] = $CONTACTS->refresh_search();
// update message count display // update message count display
$OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size)); $OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size));
$OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result->count)); $OUTPUT->command('set_rowcount', rcmail_get_rowcount_text($result->count));

@ -31,48 +31,143 @@ if ($CONTACTS->readonly) {
} }
function rcmail_contact_edithead($attrib)
{
global $RCMAIL, $CONTACTS;
// check if we have a valid result
if ($RCMAIL->action != 'add'
&& !(($result = $CONTACTS->get_result()) && ($record = $result->first()))
) {
$RCMAIL->output->show_message('contactnotfound');
return false;
}
$i_size = !empty($attrib['size']) ? $attrib['size'] : 20;
$form = array(
'head' => array(
'content' => array(
'prefix' => array('size' => $i_size),
'firstname' => array('size' => $i_size, 'visible' => true),
'middlename' => array('size' => $i_size),
'surname' => array('size' => $i_size, 'visible' => true),
'suffix' => array('size' => $i_size),
'name' => array('size' => 2*$i_size),
'nickname' => array('size' => 2*$i_size),
'company' => array('size' => $i_size),
'department' => array('size' => $i_size),
'jobtitle' => array('size' => $i_size),
)
)
);
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form'], $attrib['name'], $attrib['size']);
// return the address edit form
$out = rcmail_contact_form($form, $record, $attrib);
return $form_start . $out . $form_end;
}
function rcmail_contact_editform($attrib) function rcmail_contact_editform($attrib)
{ {
global $RCMAIL, $CONTACTS, $OUTPUT; global $RCMAIL, $CONTACTS, $CONTACT_COLTYPES;
// check if we have a valid result // check if we have a valid result
if ($RCMAIL->action != 'add' if ($RCMAIL->action != 'add'
&& !(($result = $CONTACTS->get_result()) && ($record = $result->first())) && !(($result = $CONTACTS->get_result()) && ($record = $result->first()))
) { ) {
$OUTPUT->show_message('contactnotfound'); $RCMAIL->output->show_message('contactnotfound');
return false; return false;
} }
// add some labels to client // add some labels to client
$OUTPUT->add_label('noemailwarning', 'nonamewarning'); $RCMAIL->output->add_label('noemailwarning', 'nonamewarning');
$i_size = !empty($attrib['size']) ? $attrib['size'] : 40; $i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
$t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 6; $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 10;
$t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40; $t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40;
$form = array( $form = array(
'info' => array( 'info' => array(
'name' => rcube_label('contactproperties'), 'name' => rcube_label('contactproperties'),
'content' => array( 'content' => array(
'name' => array('type' => 'text', 'size' => $i_size), 'gender' => array('visible' => false),
'firstname' => array('type' => 'text', 'size' => $i_size), 'maidenname' => array('size' => $i_size),
'surname' => array('type' => 'text', 'size' => $i_size), 'email' => array('size' => $i_size, 'visible' => true),
'email' => array('type' => 'text', 'size' => $i_size), 'phone' => array('size' => $i_size, 'visible' => true),
'address' => array('visible' => true),
'birthday' => array('size' => 12),
'anniversary' => array('size' => $i_size),
'website' => array('size' => $i_size),
'im' => array('size' => $i_size),
'manager' => array('size' => $i_size),
'assistant' => array('size' => $i_size),
'spouse' => array('size' => $i_size),
), ),
), ),
); );
if (isset($CONTACT_COLTYPES['notes'])) {
$form['notes'] = array(
'name' => rcube_label('notes'),
'content' => array(
'notes' => array('size' => $t_cols, 'rows' => $t_rows, 'label' => false, 'visible' => true, 'limit' => 1),
),
'single' => true,
);
}
list($form_start, $form_end) = get_form_tags($attrib); list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']); unset($attrib['form']);
// return the complete address edit form as table // return the complete address edit form as table
$out = rcmail_contact_form($form, $record); $out = rcmail_contact_form($form, $record, $attrib);
return $form_start . $out . $form_end; return $form_start . $out . $form_end;
} }
function rcmail_upload_photo_form($attrib)
{
global $OUTPUT;
// add ID if not given
if (!$attrib['id'])
$attrib['id'] = 'rcmUploadbox';
// find max filesize value
$max_filesize = parse_bytes(ini_get('upload_max_filesize'));
$max_postsize = parse_bytes(ini_get('post_max_size'));
if ($max_postsize && $max_postsize < $max_filesize)
$max_filesize = $max_postsize;
$max_filesize = show_bytes($max_filesize);
$hidden = new html_hiddenfield(array('name' => '_cid', 'value' => $GLOBALS['cid']));
$input = new html_inputfield(array('type' => 'file', 'name' => '_photo', 'size' => $attrib['size']));
$button = new html_inputfield(array('type' => 'button'));
$out = html::div($attrib,
$OUTPUT->form_tag(array('name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'),
$hidden->show() .
html::div(null, $input->show()) .
html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) .
html::div('buttons',
$button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' .
$button->show(rcube_label('upload'), array('class' => 'button mainaction', 'onclick' => JS_OBJECT_NAME . ".command('upload-photo', this.form)"))
)
)
);
$OUTPUT->add_label('addphoto','replacephoto');
$OUTPUT->add_gui_object('uploadbox', $attrib['id']);
return $out;
}
// similar function as in /steps/settings/edit_identity.inc // similar function as in /steps/settings/edit_identity.inc
function get_form_tags($attrib) function get_form_tags($attrib)
{ {
@ -103,7 +198,10 @@ function get_form_tags($attrib)
} }
$OUTPUT->add_handler('contactedithead', 'rcmail_contact_edithead');
$OUTPUT->add_handler('contacteditform', 'rcmail_contact_editform'); $OUTPUT->add_handler('contacteditform', 'rcmail_contact_editform');
$OUTPUT->add_handler('contactphoto', 'rcmail_contact_photo');
$OUTPUT->add_handler('photouploadform', 'rcmail_upload_photo_form');
if (!$CONTACTS->get_result() && $OUTPUT->template_exists('contactadd')) if (!$CONTACTS->get_result() && $OUTPUT->template_exists('contactadd'))
$OUTPUT->send('contactadd'); $OUTPUT->send('contactadd');

@ -30,13 +30,24 @@ header('Content-Type: text/x-vcard; charset='.RCMAIL_CHARSET);
header('Content-Disposition: attachment; filename="rcube_contacts.vcf"'); header('Content-Disposition: attachment; filename="rcube_contacts.vcf"');
while ($result && ($row = $result->next())) { while ($result && ($row = $result->next())) {
$vcard = new rcube_vcard($row['vcard']); // we already have a vcard record
$vcard->set('displayname', $row['name']); if ($row['vcard']) {
$vcard->set('firstname', $row['firstname']); echo $row['vcard'];
$vcard->set('surname', $row['surname']); }
$vcard->set('email', $row['email']); // copy values into vcard object
else {
echo $vcard->export(); $vcard = new rcube_vcard($row['vcard']);
$vcard->reset();
foreach ($row as $key => $values) {
list($field, $section) = explode(':', $key);
foreach ((array)$values as $value) {
if (is_array($value) || strlen($value))
$vcard->set($field, $value, strtoupper($section));
}
}
echo $vcard->export();
}
} }
exit; exit;

@ -56,6 +56,56 @@ if (!$OUTPUT->ajax_call) {
} }
// general definition of contact coltypes
$CONTACT_COLTYPES = array(
'name' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('name')),
'firstname' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('firstname')),
'surname' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('surname')),
'middlename' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('middlename')),
'prefix' => array('type' => 'text', 'size' => 8, 'limit' => 1, 'label' => rcube_label('nameprefix')),
'suffix' => array('type' => 'text', 'size' => 8, 'limit' => 1, 'label' => rcube_label('namesuffix')),
'nickname' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('nickname')),
'jobtitle' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('jobtitle')),
'organization' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('organization')),
'department' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('department')),
'gender' => array('type' => 'select', 'limit' => 1, 'label' => rcube_label('gender'), 'options' => array('male' => rcube_label('male'), 'female' => rcube_label('female'))),
'maidenname' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('maidenname')),
'email' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('email'), 'subtypes' => array('home','work','other')),
'phone' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('phone'), 'subtypes' => array('home','home2','work','work2','mobile','main','homefax','workfax','car','pager','video','assistant','other')),
'address' => array('type' => 'composite', 'label' => rcube_label('address'), 'subtypes' => array('home','work','other'), 'childs' => array(
'street' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('street')),
'locality' => array('type' => 'text', 'size' => 28, 'label' => rcube_label('locality')),
'zipcode' => array('type' => 'text', 'size' => 8, 'label' => rcube_label('zipcode')),
'region' => array('type' => 'text', 'size' => 12, 'label' => rcube_label('region')),
'country' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('country')),
)),
'birthday' => array('type' => 'date', 'size' => 12, 'label' => rcube_label('birthday'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col'),
'anniversary' => array('type' => 'date', 'size' => 12, 'label' => rcube_label('anniversary'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col'),
'website' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('website'), 'subtypes' => array('homepage','work','blog','other')),
'im' => array('type' => 'text', 'size' => 40, 'label' => rcube_label('instantmessenger'), 'subtypes' => array('aim','icq','msn','yahoo','jabber','skype','other')),
'notes' => array('type' => 'textarea', 'size' => 40, 'rows' => 15, 'label' => rcube_label('notes'), 'limit' => 1),
'photo' => array('type' => 'image', 'limit' => 1),
'assistant' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('assistant')),
'manager' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('manager')),
'spouse' => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('spouse')),
// TODO: define fields for vcards like GEO, KEY
);
// reduce/extend $CONTACT_COLTYPES with specification from the current $CONTACT object
if (is_array($CONTACTS->coltypes)) {
// remove cols not listed by the backend class
$contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes;
$CONTACT_COLTYPES = array_intersect_key($CONTACT_COLTYPES, $contact_cols);
// add associative coltypes definition
if (!$CONTACTS->coltypes[0]) {
foreach ($CONTACTS->coltypes as $col => $colprop)
$CONTACT_COLTYPES[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], $colprop) : $colprop;
}
}
$OUTPUT->set_env('photocol', is_array($CONTACT_COLTYPES['photo']));
function rcmail_directory_list($attrib) function rcmail_directory_list($attrib)
{ {
global $RCMAIL, $OUTPUT; global $RCMAIL, $OUTPUT;
@ -72,23 +122,21 @@ function rcmail_directory_list($attrib)
html::a(array('href' => '%s', html::a(array('href' => '%s',
'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s')); 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s'));
if (!$current && strtolower($RCMAIL->config->get('address_book_type', 'sql')) != 'ldap') { // currently selected is the first address source in the list
$current = '0'; if (!isset($current))
} $current = strval(key((array)$OUTPUT->env['address_sources']));
else if (!$current) {
// DB address book not used, see if a source is set, if not use the
// first LDAP directory.
$current = key((array)$RCMAIL->config->get('ldap_public', array()));
}
foreach ((array)$OUTPUT->env['address_sources'] as $j => $source) { foreach ((array)$OUTPUT->env['address_sources'] as $j => $source) {
$id = $source['id'] ? $source['id'] : $j; $id = strval($source['id'] ? $source['id'] : $j);
$js_id = JQ($id); $js_id = JQ($id);
$dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id); $dom_id = preg_replace('/[^a-z0-9\-_]/i', '_', $id);
$out .= sprintf($line_templ, $dom_id, ($current == $id ? 'selected' : ''), $out .= sprintf($line_templ, $dom_id, ($current === $id ? 'selected' : ''),
Q(rcmail_url(null, array('_source' => $id))), Q(rcmail_url(null, array('_source' => $id))),
$js_id, (!empty($source['name']) ? Q($source['name']) : Q($id))); $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id)));
$groupdata = rcmail_contact_groups(array('out' => $out, 'jsdata' => $jsdata, 'source' => $id));
$groupdata = array('out' => $out, 'jsdata' => $jsdata, 'source' => $id);
if ($source['groups'])
$groupdata = rcmail_contact_groups($groupdata);
$jsdata = $groupdata['jsdata']; $jsdata = $groupdata['jsdata'];
$out = $groupdata['out']; $out = $groupdata['out'];
} }
@ -130,16 +178,16 @@ function rcmail_contacts_list($attrib)
{ {
global $CONTACTS, $OUTPUT; global $CONTACTS, $OUTPUT;
// define list of cols to be displayed
$a_show_cols = array('name');
// count contacts for this user // count contacts for this user
$result = $CONTACTS->list_records(); $result = $CONTACTS->list_records($a_show_cols);
// add id to message list table if not specified // add id to message list table if not specified
if (!strlen($attrib['id'])) if (!strlen($attrib['id']))
$attrib['id'] = 'rcmAddressList'; $attrib['id'] = 'rcmAddressList';
// define list of cols to be displayed
$a_show_cols = array('name');
// create XHTML table // create XHTML table
$out = rcube_table_output($attrib, $result->records, $a_show_cols, $CONTACTS->primary_key); $out = rcube_table_output($attrib, $result->records, $a_show_cols, $CONTACTS->primary_key);
@ -233,9 +281,9 @@ function rcmail_get_rowcount_text()
} }
function rcmail_contact_form($form, $record) function rcmail_contact_form($form, $record, $attrib = null)
{ {
global $RCMAIL; global $RCMAIL, $CONFIG;
// Allow plugins to modify contact form content // Allow plugins to modify contact form content
$plugin = $RCMAIL->plugins->exec_hook('contact_form', array( $plugin = $RCMAIL->plugins->exec_hook('contact_form', array(
@ -243,35 +291,222 @@ function rcmail_contact_form($form, $record)
$form = $plugin['form']; $form = $plugin['form'];
$record = $plugin['record']; $record = $plugin['record'];
$edit_mode = $RCMAIL->action != 'show';
$del_button = $attrib['deleteicon'] ? html::img(array('src' => $CONFIG['skin_path'] . $attrib['deleteicon'], 'alt' => rcube_label('delete'))) : rcube_label('delete');
unset($attrib['deleteicon']);
$out = ''; $out = '';
// get default coltypes
$coltypes = $GLOBALS['CONTACT_COLTYPES'];
$coltype_lables = array();
foreach ($coltypes as $col => $prop) {
if ($prop['subtypes']) {
$select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype'));
$select_subtype->add($prop['subtypes']);
$coltypes[$col]['subtypes_select'] = $select_subtype->show();
}
if ($prop['childs']) {
foreach ($prop['childs'] as $childcol => $cp)
$coltype_lables[$childcol] = array('label' => $cp['label']);
}
}
foreach ($form as $fieldset) { foreach ($form as $section => $fieldset) {
// skip empty sections
if (empty($fieldset['content'])) if (empty($fieldset['content']))
continue; continue;
$select_add = new html_select(array('class' => 'addfieldmenu', 'rel' => $section));
$select_add->add(rcube_label('addfield'), '');
// render head section with name fields (not a regular list of rows)
if ($section == 'head') {
$content = '';
$names_arr = array($record['prefix'], $record['firstname'], $record['middlename'], $record['surname'], $record['suffix']);
if ($record['name'] == join(' ', array_filter($names_arr)))
unset($record['name']);
// group fields
$field_blocks = array(
'names' => array('prefix','firstname','middlename','surname','suffix'),
'displayname' => array('name'),
'nickname' => array('nickname'),
'jobnames' => array('organization','department','jobtitle'),
);
foreach ($field_blocks as $blockname => $colnames) {
$fields = '';
foreach ($colnames as $col) {
// skip cols unknown to the backend
if (!$coltypes[$col])
continue;
if ($RCMAIL->action == 'show') {
if (!empty($record[$col]))
$fields .= html::span('namefield ' . $col, Q($record[$col])) . " ";
}
else {
$colprop = (array)$fieldset['content'][$col] + (array)$coltypes[$col];
$colprop['id'] = 'ff_'.$col;
if (empty($record[$col]) && !$colprop['visible']) {
$colprop['style'] = 'display:none';
$select_add->add($colprop['label'], $col);
}
$fields .= rcmail_get_edit_field($col, $record[$col], $colprop, $colprop['type']);
}
}
$content .= html::div($blockname, $fields);
}
if ($edit_mode)
$content .= html::p('addfield', $select_add->show(null));
$out .= html::tag('fieldset', $attrib, (!empty($fieldset['name']) ? html::tag('legend', null, Q($fieldset['name'])) : '') . $content) ."\n";
continue;
}
$content = ''; $content = '';
if (is_array($fieldset['content'])) { if (is_array($fieldset['content'])) {
$table = new html_table(array('cols' => 2));
foreach ($fieldset['content'] as $col => $colprop) { foreach ($fieldset['content'] as $col => $colprop) {
$colprop['id'] = 'rcmfd_'.$col; // remove subtype part of col name
list($field, $subtype) = explode(':', $col);
$label = !empty($colprop['label']) ? $colprop['label'] : rcube_label($col); if (!$subtype) $subtype = 'home';
$fullkey = $col.':'.$subtype;
// skip cols unknown to the backend
if (!$coltypes[$field])
continue;
// merge colprop with global coltype configuration
$colprop += $coltypes[$field];
$label = isset($colprop['label']) ? $colprop['label'] : rcube_label($col);
// prepare subtype selector in edit mode
if ($edit_mode && is_array($colprop['subtypes'])) {
$select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype'));
$select_subtype->add($colprop['subtypes']);
}
else
$select_subtype = null;
if (!empty($colprop['value'])) { if (!empty($colprop['value'])) {
$value = $colprop['value']; $values = (array)$colprop['value'];
}
else if ($RCMAIL->action == 'show') {
$value = $record[$col];
} }
else { else {
$value = rcmail_get_edit_field($col, $record[$col], $colprop, $colprop['type']); // iterate over possible subtypes and collect values with their subtype
if (is_array($colprop['subtypes'])) {
$values = $subtypes = array();
foreach ($colprop['subtypes'] as $i => $st) {
$newval = false;
if ($record[$field.':'.$st]) {
$subtypes[count($values)] = $st;
$newval = $record[$field.':'.$st];
}
else if ($i == 0 && $record[$field]) {
$subtypes[count($values)] = $st;
$newval = $record[$field];
}
if ($newval !== false) {
if (is_array($newval) && isset($newval[0]))
$values = array_merge($values, $newval);
else
$values[] = $newval;
}
}
}
else {
$values = $record[$fullkey] ? $record[$fullkey] : $record[$field];
$subtypes = null;
}
} }
$table->add('title', sprintf('<label for="%s">%s</label>', $colprop['id'], Q($label))); // hack: create empty values array to force this field to be displayed
$table->add(null, $value); if (empty($values) && $colprop['visible'])
$values[] = '';
$rows = '';
foreach ((array)$values as $i => $val) {
if ($subtypes[$i])
$subtype = $subtypes[$i];
// render composite field
if ($colprop['type'] == 'composite') {
$composite = array(); $j = 0;
$template = $RCMAIL->config->get($col . '_template', '{'.join('} {', array_keys($colprop['childs'])).'}');
foreach ($colprop['childs'] as $childcol => $cp) {
$childvalue = $val[$childcol] ? $val[$childcol] : $val[$j];
if ($edit_mode) {
if ($colprop['subtypes'] || $colprop['limit'] != 1) $cp['array'] = true;
$composite['{'.$childcol.'}'] = rcmail_get_edit_field($childcol, $childvalue, $cp, $cp['type']) . " ";
}
else {
$childval = $cp['render_func'] ? call_user_func($cp['render_func'], $childvalue, $childcol) : Q($childvalue);
$composite['{'.$childcol.'}'] = html::span('data ' . $childcol, $childval) . " ";
}
$j++;
}
$coltypes[$field] += (array)$colprop;
$coltypes[$field]['count']++;
$val = strtr($template, $composite);
}
else if ($edit_mode) {
// call callback to render/format value
if ($colprop['render_func'])
$val = call_user_func($colprop['render_func'], $val, $col);
$coltypes[$field] = (array)$colprop + $coltypes[$field];
if ($colprop['subtypes'] || $colprop['limit'] != 1)
$colprop['array'] = true;
$val = rcmail_get_edit_field($col, $val, $colprop, $colprop['type']);
$coltypes[$field]['count']++;
}
else if ($colprop['render_func'])
$val = call_user_func($colprop['render_func'], $val, $col);
else if (is_array($colprop['options']) && isset($colprop['options'][$val]))
$val = $colprop['options'][$val];
else
$val = Q($val);
// use subtype as label
if ($colprop['subtypes'])
$label = $subtype;
// add delete button/link
if ($edit_mode && !($colprop['visible'] && $colprop['limit'] == 1))
$val .= html::a(array('href' => '#del', 'class' => 'contactfieldbutton deletebutton', 'title' => rcube_label('delete'), 'rel' => $col), $del_button);
// display row with label
if ($label) {
$rows .= html::div('row',
html::div('contactfieldlabel label', $select_subtype ? $select_subtype->show($subtype) : Q($label)) .
html::div('contactfieldcontent '.$colprop['type'], $val));
}
else // row without label
$rows .= html::div('row', html::div('contactfield', $val));
}
// add option to the add-field menu
if (!$colprop['limit'] || $coltypes[$field]['count'] < $colprop['limit']) {
$select_add->add($colprop['label'], $col);
$select_add->_count++;
}
// wrap rows in fieldgroup container
$content .= html::tag('fieldset', array('class' => 'contactfieldgroup contactcontroller' . $col, 'style' => ($rows ? null : 'display:none')),
($colprop['subtypes'] ? html::tag('legend', null, Q($colprop['label'])) : ' ') .
$rows);
} }
$content = $table->show();
// also render add-field selector
if ($edit_mode)
$content .= html::p('addfield', $select_add->show(null, array('style' => $select_add->_count ? null : 'display:none')));
$content = html::div(array('id' => 'contactsection' . $section), $content);
} }
else { else {
$content = $fieldset['content']; $content = $fieldset['content'];
@ -279,11 +514,53 @@ function rcmail_contact_form($form, $record)
$out .= html::tag('fieldset', null, html::tag('legend', null, Q($fieldset['name'])) . $content) ."\n"; $out .= html::tag('fieldset', null, html::tag('legend', null, Q($fieldset['name'])) . $content) ."\n";
} }
if ($edit_mode) {
$RCMAIL->output->set_env('coltypes', $coltypes + $coltype_lables);
$RCMAIL->output->set_env('delbutton', $del_button);
$RCMAIL->output->add_label('delete');
}
return $out; return $out;
} }
function rcmail_contact_photo($attrib)
{
global $CONTACTS, $CONTACT_COLTYPES, $RCMAIL, $CONFIG;
if ($result = $CONTACTS->get_result())
$record = $result->first();
$photo_img = $attrib['placeholder'] ? $CONFIG['skin_path'] . $attrib['placeholder'] : 'program/blank.gif';
unset($attrib['placeholder']);
if ($CONTACT_COLTYPES['photo']) {
$RCMAIL->output->set_env('photo_placeholder', $photo_img);
if ($record['photo'])
$photo_img = $RCMAIL->url(array('_action' => 'photo', '_cid' => $record['ID'], '_source' => $_REQUEST['_source']));
$img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => ''));
$content = html::div($attrib, $img);
if ($RCMAIL->action == 'edit' || $RCMAIL->action == 'add') {
$RCMAIL->output->add_gui_object('contactphoto', $attrib['id']);
$hidden = new html_hiddenfield(array('name' => '_photo', 'id' => 'ff_photo'));
$content .= $hidden->show();
}
}
return $content;
}
function rcmail_format_date_col($val)
{
global $RCMAIL;
return format_date($val, $RCMAIL->config->get('date_format', 'Y-m-d'));
}
// register UI objects // register UI objects
$OUTPUT->add_handlers(array( $OUTPUT->add_handlers(array(
'directorylist' => 'rcmail_directory_list', 'directorylist' => 'rcmail_directory_list',
@ -297,6 +574,8 @@ $OUTPUT->add_handlers(array(
// register action aliases // register action aliases
$RCMAIL->register_action_map(array( $RCMAIL->register_action_map(array(
'add' => 'edit.inc', 'add' => 'edit.inc',
'photo' => 'show.inc',
'upload-photo' => 'save.inc',
'group-create' => 'groups.inc', 'group-create' => 'groups.inc',
'group-rename' => 'groups.inc', 'group-rename' => 'groups.inc',
'group-delete' => 'groups.inc', 'group-delete' => 'groups.inc',

@ -79,8 +79,7 @@ else if ($RCMAIL->action == 'group-create') {
if ($created && $OUTPUT->ajax_call) { if ($created && $OUTPUT->ajax_call) {
$OUTPUT->show_message('groupcreated', 'confirmation'); $OUTPUT->show_message('groupcreated', 'confirmation');
$OUTPUT->command('insert_contact_group', array( $OUTPUT->command('insert_contact_group', array('source' => $source) + $created);
'source' => $source, 'id' => $created['id'], 'name' => $created['name']));
} }
else if (!$created) { else if (!$created) {
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error'); $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error');

@ -150,13 +150,8 @@ if ($_FILES['_file']['tmp_name'] && is_uploaded_file($_FILES['_file']['tmp_name'
} }
} }
$a_record = array( $a_record = $vcard->get_assoc();
'name' => $vcard->displayname, $a_record['vcard'] = $vcard->export();
'firstname' => $vcard->firstname,
'surname' => $vcard->surname,
'email' => $email,
'vcard' => $vcard->export(),
);
$plugin = $RCMAIL->plugins->exec_hook('contact_create', array('record' => $a_record, 'source' => null)); $plugin = $RCMAIL->plugins->exec_hook('contact_create', array('record' => $a_record, 'source' => null));
$a_record = $plugin['record']; $a_record = $plugin['record'];

@ -20,7 +20,7 @@
*/ */
// get contacts for this user // get contacts for this user
$result = $CONTACTS->list_records(); $result = $CONTACTS->list_records(array('name'));
// update message count display // update message count display
$OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size)); $OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size));

@ -29,8 +29,10 @@ if ($cid && preg_match('/^[a-z0-9\+\/=_-]+(,[a-z0-9\+\/=_-]+)*$/i', $cid) && $CO
$CONTACTS->set_pagesize(100); $CONTACTS->set_pagesize(100);
$recipients = $CONTACTS->search($CONTACTS->primary_key, $cid); $recipients = $CONTACTS->search($CONTACTS->primary_key, $cid);
while (is_object($recipients) && ($rec = $recipients->iterate())) while (is_object($recipients) && ($rec = $recipients->iterate())) {
$mailto[] = format_email_recipient($rec['email'], $rec['name']); $emails = $CONTACTS->get_col_values('email', $rec, true);
$mailto[] = format_email_recipient($emails[0], $rec['name']);
}
} }
if (!empty($mailto)) if (!empty($mailto))

@ -5,7 +5,7 @@
| program/steps/addressbook/save.inc | | program/steps/addressbook/save.inc |
| | | |
| This file is part of the Roundcube Webmail client | | This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2009, The Roundcube Dev Team | | Copyright (C) 2005-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL | | Licensed under the GNU GPL |
| | | |
| PURPOSE: | | PURPOSE: |
@ -29,33 +29,147 @@ if ($CONTACTS->readonly) {
return; return;
} }
// Basic input checks
if ((!get_input_value('_name', RCUBE_INPUT_POST) || !get_input_value('_email', RCUBE_INPUT_POST))) {
$OUTPUT->show_message('formincomplete', 'warning');
rcmail_overwrite_action($return_action);
return;
}
// handle photo upload for contacts
if ($RCMAIL->action == 'upload-photo') {
// clear all stored output properties (like scripts and env vars)
$OUTPUT->reset();
if ($filepath = $_FILES['_photo']['tmp_name']) {
// check file type and resize image
$imageprop = rcmail::imageprops($_FILES['_photo']['tmp_name']);
if ($imageprop['width'] && $imageprop['height']) {
$maxsize = intval($RCMAIL->config->get('contact_photo_size', 160));
$tmpfname = tempnam($RCMAIL->config->get('temp_dir'), 'rcmImgConvert');
$save_hook = 'attachment_upload';
// scale image to a maximum size
if (($imageprop['width'] > $maxsize || $imageprop['height'] > $maxsize) &&
(rcmail::imageconvert(array('in' => $filepath, 'out' => $tmpfname, 'size' => $maxsize.'x'.$maxsize, 'type' => $imageprop['type'])) !== false)) {
$filepath = $tmpfname;
$save_hook = 'attachment_save';
}
// save uploaded file in storage backend
$attachment = $RCMAIL->plugins->exec_hook($save_hook, array(
'path' => $filepath,
'size' => $_FILES['_photo']['size'],
'name' => $_FILES['_photo']['name'],
'mimetype' => 'image/' . $imageprop['type'],
));
}
else
$attachment['error'] = rcube_label('invalidimageformat');
if ($attachment['status'] && !$attachment['abort']) {
$file_id = $attachment['id'];
$_SESSION['contacts']['files'][$file_id] = $attachment;
$OUTPUT->command('replace_contact_photo', $file_id);
}
else { // upload failed
$err = $_FILES['_photo']['error'];
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE)
$msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array('size' => show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
else if ($attachment['error'])
$msg = $attachment['error'];
else
$msg = rcube_label('fileuploaderror');
$OUTPUT->command('display_message', $msg, 'error');
}
}
else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// if filesize exceeds post_max_size then $_FILES array is empty,
// show filesizeerror instead of fileuploaderror
if ($maxsize = ini_get('post_max_size'))
$msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array('size' => show_bytes(parse_bytes($maxsize)))));
else
$msg = rcube_label('fileuploaderror');
$OUTPUT->command('display_message', $msg, 'error');
}
$OUTPUT->command('photo_upload_end');
$OUTPUT->send('iframe');
}
// setup some vars we need
$a_save_cols = array('name', 'firstname', 'surname', 'email');
$a_record = array();
// read POST values into hash array // read POST values into hash array
foreach ($a_save_cols as $col) { $a_record = array();
foreach ($GLOBALS['CONTACT_COLTYPES'] as $col => $colprop) {
$fname = '_'.$col; $fname = '_'.$col;
if (isset($_POST[$fname])) if ($colprop['composite'])
continue;
// gather form data of composite fields
if ($colprop['childs']) {
$values = array();
foreach ($colprop['childs'] as $childcol => $cp) {
$vals = get_input_value('_'.$childcol, RCUBE_INPUT_POST);
foreach ((array)$vals as $i => $val)
$values[$i][$childcol] = $val;
}
$subtypes = get_input_value('_subtype_' . $col, RCUBE_INPUT_POST);
foreach ($subtypes as $i => $subtype)
if ($values[$i])
$a_record[$col.':'.$subtype][] = $values[$i];
}
// assign values and subtypes
else if (is_array($_POST[$fname])) {
$values = get_input_value($fname, RCUBE_INPUT_POST);
$subtypes = get_input_value('_subtype_' . $col, RCUBE_INPUT_POST);
foreach ($values as $i => $val) {
$subtype = $subtypes[$i] ? ':'.$subtypes[$i] : '';
$a_record[$col.$subtype][] = $val;
}
}
else if (isset($_POST[$fname])) {
$a_record[$col] = get_input_value($fname, RCUBE_INPUT_POST); $a_record[$col] = get_input_value($fname, RCUBE_INPUT_POST);
}
} }
// Validity checks if (empty($a_record['name']))
$_email = idn_to_ascii($a_record['email']); $a_record['name'] = join(' ', array_filter(array($a_record['prefix'], $a_record['firstname'], $a_record['middlename'], $a_record['surname'], $a_record['suffix'],)));
if (!check_email($_email, false)) {
$OUTPUT->show_message('emailformaterror', 'warning', array('email' => $_email)); #var_dump($a_record);
// Basic input checks (TODO: delegate to $CONTACTS instance)
if (empty($a_record['name'])/* || empty($a_record['email'])*/) {
$OUTPUT->show_message('formincomplete', 'warning');
rcmail_overwrite_action($return_action); rcmail_overwrite_action($return_action);
return; return;
} }
// Validity checks
foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) {
if (strlen($email)) {
$_email = idn_to_ascii($email);
if (!check_email($_email, false)) {
$OUTPUT->show_message('emailformaterror', 'warning', array('email' => $email));
rcmail_overwrite_action($return_action);
return;
}
}
}
// get raw photo data if changed
if (isset($a_record['photo'])) {
if ($a_record['photo'] == '-del-') {
$a_record['photo'] = '';
}
else if ($tempfile = $_SESSION['contacts']['files'][$a_record['photo']]) {
$tempfile = $RCMAIL->plugins->exec_hook('attachment_get', $tempfile);
if ($tempfile['status'])
$a_record['photo'] = $tempfile['data'] ? $tempfile['data'] : @file_get_contents($tempfile['path']);
}
else
unset($a_record['photo']);
// cleanup session data
$RCMAIL->plugins->exec_hook('attachments_cleanup', array());
$RCMAIL->session->remove('contacts');
}
// update an existing contact // update an existing contact
if (!empty($cid)) if (!empty($cid))
{ {
@ -92,7 +206,8 @@ if (!empty($cid))
} }
else { else {
// show error message // show error message
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error', null, false); $err = $CONTACTS->get_error();
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : ($err['message'] ? $err['message'] : 'errorsaving'), 'error', null, false);
rcmail_overwrite_action('show'); rcmail_overwrite_action('show');
} }
} }
@ -100,10 +215,16 @@ if (!empty($cid))
// insert a new contact // insert a new contact
else { else {
// check for existing contacts // check for existing contacts
$existing = $CONTACTS->search('email', $a_record['email'], true, false); $existing = false;
foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) {
if (($res = $CONTACTS->search('email', $email, true, false)) && $res->count) {
$existing = true;
break;
}
}
// show warning message // show warning message
if ($existing->count) { if ($existing) {
$OUTPUT->show_message('contactexists', 'warning', null, false); $OUTPUT->show_message('contactexists', 'warning', null, false);
rcmail_overwrite_action('add'); rcmail_overwrite_action('add');
return; return;
@ -138,7 +259,8 @@ else {
} }
else { else {
// show error message // show error message
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error', null, false); $err = $CONTACTS->get_error();
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : ($err['message'] ? $err['message'] : 'errorsaving'), 'error', null, false);
rcmail_overwrite_action('add'); rcmail_overwrite_action('add');
} }
} }

@ -5,7 +5,7 @@
| program/steps/addressbook/search.inc | | program/steps/addressbook/search.inc |
| | | |
| This file is part of the Roundcube Webmail client | | This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2007, The Roundcube Dev Team | | Copyright (C) 2005-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL | | Licensed under the GNU GPL |
| | | |
| PURPOSE: | | PURPOSE: |
@ -28,11 +28,11 @@ $search_request = md5('addr'.$search);
// get contacts for this user // get contacts for this user
$result = $CONTACTS->search(array('name','email'), $search); $result = $CONTACTS->search(array('name','email'), $search);
// save search settings in session
$_SESSION['search'][$search_request] = $CONTACTS->get_search_set();
if ($result->count > 0) if ($result->count > 0)
{ {
// save search settings in session
$_SESSION['search'][$search_request] = $CONTACTS->get_search_set();
// create javascript list // create javascript list
rcmail_js_contacts_list($result); rcmail_js_contacts_list($result);
} }

@ -25,8 +25,30 @@ if (($cid = get_input_value('_cid', RCUBE_INPUT_GPC)) && ($record = $CONTACTS->g
$OUTPUT->set_env('cid', $record['ID']); $OUTPUT->set_env('cid', $record['ID']);
} }
// return raw photo of the given contact
if ($RCMAIL->action == 'photo') {
if (($file_id = get_input_value('_photo', RCUBE_INPUT_GPC)) && ($tempfile = $_SESSION['contacts']['files'][$file_id])) {
$tempfile = $RCMAIL->plugins->exec_hook('attachment_display', $tempfile);
if ($tempfile['status']) {
if ($tempfile['data'])
$data = $tempfile['data'];
else if ($tempfile['path'])
$data = file_get_contents($tempfile['path']);
}
}
else if ($record['photo']) {
$data = is_array($record['photo']) ? $record['photo'][0] : $record['photo'];
if (!preg_match('![^a-z0-9/=+-]!i', $data))
$data = base64_decode($data, true);
}
header('Content-Type: ' . rc_image_content_type($data));
echo $data ? $data : file_get_contents('program/blank.gif');
exit;
}
function rcmail_contact_details($attrib)
function rcmail_contact_head($attrib)
{ {
global $CONTACTS, $RCMAIL; global $CONTACTS, $RCMAIL;
@ -36,54 +58,99 @@ function rcmail_contact_details($attrib)
return false; return false;
} }
$i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
$t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 6;
$t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40;
$microformats = array('name' => 'fn', 'email' => 'email'); $microformats = array('name' => 'fn', 'email' => 'email');
$form = array( $form = array(
'info' => array( 'head' => array( // section 'head' is magic!
'name' => rcube_label('contactproperties'),
'content' => array( 'content' => array(
'name' => array('type' => 'text', 'size' => $i_size), 'prefix' => array('type' => 'text'),
'firstname' => array('type' => 'text', 'size' => $i_size), 'firstname' => array('type' => 'text'),
'surname' => array('type' => 'text', 'size' => $i_size), 'middlename' => array('type' => 'text'),
'email' => array('type' => 'text', 'size' => $i_size), 'surname' => array('type' => 'text'),
'suffix' => array('type' => 'text'),
), ),
), ),
'groups' => array(
'name' => rcube_label('groups'),
'content' => '',
),
); );
// Get content of groups fieldset unset($attrib['name']);
if ($groups = rcmail_contact_record_groups($record['ID'])) { return rcmail_contact_form($form, $record, $attrib);
$form['groups']['content'] = $groups; }
}
else {
unset($form['groups']); function rcmail_contact_details($attrib)
{
global $CONTACTS, $RCMAIL, $CONTACT_COLTYPES;
// check if we have a valid result
if (!(($result = $CONTACTS->get_result()) && ($record = $result->first()))) {
//$RCMAIL->output->show_message('contactnotfound');
return false;
} }
if (!empty($record['email'])) { $i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
$form['info']['content']['email']['value'] = html::a(array(
'href' => 'mailto:' . $record['email'], $form = array(
'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($record['email'])), 'info' => array(
'title' => rcube_label('composeto'), 'name' => rcube_label('contactproperties'),
'class' => $microformats['email'], 'content' => array(
), Q($record['email'])); 'gender' => array('size' => $i_size),
'maidenname' => array('size' => $i_size),
'email' => array('size' => $i_size, 'render_func' => 'rcmail_render_email_value'),
'phone' => array('size' => $i_size),
'address' => array(),
'birthday' => array('size' => $i_size),
'anniversary' => array('size' => $i_size),
'website' => array('size' => $i_size, 'render_func' => 'rcmail_render_url_value'),
'im' => array('size' => $i_size),
'manager' => array('size' => $i_size),
'assistant' => array('size' => $i_size),
'spouse' => array('size' => $i_size),
),
),
);
if (isset($CONTACT_COLTYPES['notes'])) {
$form['notes'] = array(
'name' => rcube_label('notes'),
'content' => array(
'notes' => array('type' => 'textarea', 'label' => false),
),
);
} }
foreach (array('name', 'firstname', 'surname') as $col) {
if ($record[$col]) { if ($CONTACTS->groups) {
$form['info']['content'][$col]['value'] = html::span($microformats[$col], Q($record[$col])); $form['groups'] = array(
} 'name' => rcube_label('groups'),
'content' => rcmail_contact_record_groups($record['ID']),
);
} }
return rcmail_contact_form($form, $record); return rcmail_contact_form($form, $record);
} }
function rcmail_render_email_value($email, $col)
{
return html::a(array(
'href' => 'mailto:' . $email,
'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($email)),
'title' => rcube_label('composeto'),
'class' => 'email',
), Q($email));
}
function rcmail_render_url_value($url, $col)
{
$prefix = preg_match('![htfps]+://!', $url) ? '' : 'http://';
return html::a(array(
'href' => $prefix . $url,
'target' => '_blank',
'class' => 'url',
), Q($url));
}
function rcmail_contact_record_groups($contact_id) function rcmail_contact_record_groups($contact_id)
{ {
global $RCMAIL, $CONTACTS, $GROUPS; global $RCMAIL, $CONTACTS, $GROUPS;
@ -124,6 +191,8 @@ function rcmail_contact_record_groups($contact_id)
//$OUTPUT->framed = $_framed; //$OUTPUT->framed = $_framed;
$OUTPUT->add_handler('contacthead', 'rcmail_contact_head');
$OUTPUT->add_handler('contactdetails', 'rcmail_contact_details'); $OUTPUT->add_handler('contactdetails', 'rcmail_contact_details');
$OUTPUT->add_handler('contactphoto', 'rcmail_contact_photo');
$OUTPUT->send('contact'); $OUTPUT->send('contact');

@ -29,8 +29,10 @@ if ($RCMAIL->action == 'group-expand') {
$abook->set_group($gid); $abook->set_group($gid);
$abook->set_pagesize(1000); // TODO: limit number of group members by config $abook->set_pagesize(1000); // TODO: limit number of group members by config
$result = $abook->list_records(array('email','name')); $result = $abook->list_records(array('email','name'));
while ($result && ($sql_arr = $result->iterate())) while ($result && ($sql_arr = $result->iterate())) {
$members[] = format_email_recipient($sql_arr['email'], $sql_arr['name']); foreach ((array)$sql_arr['email'] as $email)
$members[] = format_email_recipient($email, $sql_arr['name']);
}
$OUTPUT->command('replace_group_recipients', $gid, join(', ', $members)); $OUTPUT->command('replace_group_recipients', $gid, join(', ', $members));
} }
@ -45,12 +47,14 @@ else if ($book_types && $search = get_input_value('_search', RCUBE_INPUT_GPC, tr
if ($result = $abook->search(array('email','name'), $search, false, true, true, 'email')) { if ($result = $abook->search(array('email','name'), $search, false, true, true, 'email')) {
while ($sql_arr = $result->iterate()) { while ($sql_arr = $result->iterate()) {
$contact = format_email_recipient($sql_arr['email'], $sql_arr['name']); foreach ((array)$abook->get_col_values('email', $sql_arr, true) as $email) {
// when we've got more than one book, we need to skip duplicates $contact = format_email_recipient($email, $sql_arr['name']);
if ($books_num == 1 || !in_array($contact, $contacts)) { // when we've got more than one book, we need to skip duplicates
$contacts[] = $contact; if ($books_num == 1 || !in_array($contact, $contacts)) {
if (count($contacts) >= $MAXNUM) $contacts[] = $contact;
break 2; if (count($contacts) >= $MAXNUM)
break 2;
}
} }
} }
} }

@ -1133,7 +1133,7 @@ function rcmail_compose_attachment_form($attrib)
$out = html::div($attrib, $out = html::div($attrib,
$OUTPUT->form_tag(array('name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'), $OUTPUT->form_tag(array('name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'),
html::div(null, rcmail_compose_attachment_field(array('size' => $attrib[attachmentfieldsize]))) . html::div(null, rcmail_compose_attachment_field(array('size' => $attrib['attachmentfieldsize']))) .
html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) . html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) .
html::div('buttons', html::div('buttons',
$button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' . $button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' .

@ -214,3 +214,166 @@ body.iframe,
text-align: right; text-align: right;
} }
#contacttabs
{
position: relative;
padding-bottom: 22px;
}
#contacttabs div.tabsbar {
top: 0;
left: 2px;
}
#contacttabs fieldset.tabbed {
position: relative;
top: 22px;
min-height: 5em;
}
#contacthead
{
margin-bottom: 1em;
border: 0;
padding: 0;
}
#contacthead .names span.namefield,
#contacthead .names input
{
font-size: 140%;
}
#contacthead .displayname span.namefield
{
font-size: 120%;
}
#contacthead span.nickname:before,
#contacthead span.nickname:after,
#contacthead input.ff_nickname:before,
#contacthead input.ff_nickname:after
{
content: '"';
}
#contacthead input
{
margin-right: 6px;
margin-bottom: 0.2em;
}
#contacthead .names input,
#contacthead .addnames input,
#contacthead .jobnames input
{
width: 180px;
}
#contacthead input.ff_prefix,
#contacthead input.ff_suffix
{
width: 90px;
}
#contacthead .addnames input.ff_name
{
width: 374px;
}
#contactphoto
{
float: right;
width: 60px;
margin-left: 3em;
margin-right: 4px;
}
#contactpic
{
width: 60px;
min-height: 60px;
border: 1px solid #ccc;
background: white;
}
#contactpic img {
width: 60px;
}
#contactphoto .formlinks
{
margin-top: 0.5em;
text-align: center;
}
fieldset.contactfieldgroup
{
border: 0;
margin: 0.5em 0;
padding: 0.5em 2px;
}
fieldset.contactfieldgroup legend
{
font-size: 0.9em;
}
.contactfieldgroup .row
{
position: relative;
margin-bottom: 0.4em;
}
.contactfieldgroup .contactfieldlabel
{
position: absolute;
top: 0;
left: 2px;
width: 90px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #666;
font-weight: bold;
}
.contactfieldgroup .contactfieldlabel select
{
width: 78px;
background: none;
border: 0;
color: #666;
font-weight: bold;
padding-left: 0;
}
.contactfieldgroup .contactfieldcontent
{
padding-left: 100px;
min-height: 1em;
line-height: 1.3em;
}
.contactfieldgroup .contactfield {
line-height: 1.3em;
}
.contactcontrolleraddress .contactfieldcontent input {
margin-bottom: 0.1em;
}
.contactfieldcontent .contactfieldbutton {
vertical-align: middle;
margin-left: 0.5em;
}
#upload-form
{
padding: 6px;
}
#upload-form div
{
padding: 2px;
}

@ -76,6 +76,12 @@ input, textarea
padding: 1px 3px; padding: 1px 3px;
} }
input.placeholder,
textarea.placeholder
{
color: #aaa;
}
input.button input.button
{ {
height: 20px; height: 20px;
@ -114,6 +120,20 @@ img
font-size: 11px; font-size: 11px;
} }
.formlinks a,
.formlinks a:visited
{
color: #CC0000;
font-size: 11px;
text-decoration: none;
}
.formlinks a.disabled,
.formlinks a.disabled:visited
{
color: #999999;
}
/** common user interface objects */ /** common user interface objects */
#mainscreen #mainscreen

@ -25,9 +25,8 @@ function rcube_show_advanced(visible)
// Warning: don't place "caller" <script> inside page element (id) // Warning: don't place "caller" <script> inside page element (id)
function rcube_init_tabs(id, current) function rcube_init_tabs(id, current)
{ {
var content = document.getElementById(id), var content = $('#'+id),
// get fieldsets of the higher-level (skip nested fieldsets) fs = content.children('fieldset');
fs = $('fieldset', content).not('fieldset > fieldset');
if (!fs.length) if (!fs.length)
return; return;
@ -42,9 +41,7 @@ function rcube_init_tabs(id, current)
// convert fildsets into tabs // convert fildsets into tabs
fs.each(function(idx) { fs.each(function(idx) {
var tab, a, elm = $(this), var tab, a, elm = $(this), legend = elm.children('legend');
// get first legend element
legend = $(elm).children('legend');
// create a tab // create a tab
a = $('<a>').text(legend.text()).attr('href', '#'); a = $('<a>').text(legend.text()).attr('href', '#');
@ -66,8 +63,7 @@ function rcube_init_tabs(id, current)
function rcube_show_tab(id, index) function rcube_show_tab(id, index)
{ {
var content = document.getElementById(id), var fs = $('#'+id).children('fieldset');
fs = $('fieldset', content).not('fieldset > fieldset');
fs.each(function(idx) { fs.each(function(idx) {
// Show/hide fieldset (tab content) // Show/hide fieldset (tab content)
@ -94,7 +90,8 @@ function rcube_mail_ui()
mailboxmenu: {id:'mailboxoptionsmenu', above:1}, mailboxmenu: {id:'mailboxoptionsmenu', above:1},
composemenu: {id:'composeoptionsmenu', editable:1}, composemenu: {id:'composeoptionsmenu', editable:1},
// toggle: #1486823, #1486930 // toggle: #1486823, #1486930
uploadmenu: {id:'attachment-form', editable:1, above:1, toggle:!bw.ie&&!bw.linux } uploadmenu: {id:'attachment-form', editable:1, above:1, toggle:!bw.ie&&!bw.linux },
uploadform: {id:'upload-form', editable:1, toggle:!bw.ie&&!bw.linux }
}; };
var obj; var obj;
@ -136,6 +133,9 @@ show_popupmenu: function(popup, show)
if (!above && pos.top + ref.offsetHeight + obj.height() > window.innerHeight) if (!above && pos.top + ref.offsetHeight + obj.height() > window.innerHeight)
above = true; above = true;
if (pos.left + obj.width() > window.innerWidth)
pos.left = window.innerWidth - obj.width() - 30;
obj.css({ left:pos.left, top:(pos.top + (above ? -obj.height() : ref.offsetHeight)) }); obj.css({ left:pos.left, top:(pos.top + (above ? -obj.height() : ref.offsetHeight)) });
} }
@ -500,6 +500,9 @@ function rcube_init_mail_ui()
if (rcmail.env.action == 'compose') if (rcmail.env.action == 'compose')
rcmail_ui.init_compose_form(); rcmail_ui.init_compose_form();
} }
else if (rcmail.env.task == 'addressbook') {
rcmail.addEventListener('afterupload-photo', function(){ rcmail_ui.show_popup('uploadform', false); });
}
} }
// Events handling in iframes (eg. preview pane) // Events handling in iframes (eg. preview pane)

@ -236,3 +236,9 @@ table.records-table thead tr td
{ {
margin-top: 2px; margin-top: 2px;
} }
.contactfieldgroup legend
{
padding: 0 0 0.5em 0;
margin-left: -4px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

@ -1342,20 +1342,6 @@ input.from_address
display: none; display: none;
} }
.formlinks a,
.formlinks a:visited
{
color: #999999;
font-size: 11px;
text-decoration: none;
}
.formlinks a,
.formlinks a:visited
{
color: #CC0000;
}
#compose-editorfooter #compose-editorfooter
{ {
position: absolute; position: absolute;

@ -9,12 +9,17 @@
<div id="contact-title" class="boxtitle"><roundcube:label name="contactproperties" /></div> <div id="contact-title" class="boxtitle"><roundcube:label name="contactproperties" /></div>
<div id="contact-details" class="boxcontent"> <div id="contact-details" class="boxcontent">
<roundcube:object name="contactdetails" /> <div id="contactphoto"><roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" /></div>
<roundcube:object name="contacthead" id="contacthead" />
<div style="clear:both"></div>
<div id="contacttabs">
<roundcube:object name="contactdetails" />
</div>
<p> <p>
<roundcube:button command="edit" type="input" class="button" label="editcontact" condition="!ENV:readonly" /> <roundcube:button command="edit" type="input" class="button" label="editcontact" condition="!ENV:readonly" />
</p> </p>
</div> </div>
<script type="text/javascript">rcube_init_tabs('contact-details')</script> <script type="text/javascript">rcube_init_tabs('contacttabs')</script>
</body> </body>
</html> </html>

@ -5,18 +5,34 @@
<roundcube:include file="/includes/links.html" /> <roundcube:include file="/includes/links.html" />
<script type="text/javascript" src="/functions.js"></script> <script type="text/javascript" src="/functions.js"></script>
</head> </head>
<body class="iframe"> <body class="iframe" onload="rcube_init_mail_ui()">
<div id="contact-title" class="boxtitle"><roundcube:label name="addcontact" /></div> <div id="contact-title" class="boxtitle"><roundcube:label name="addcontact" /></div>
<div id="contact-details" class="boxcontent"> <div id="contact-details" class="boxcontent">
<roundcube:object name="contacteditform" size="40" /> <form name="editform" method="post" action="./">
<div id="contactphoto">
<roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" />
<div class="formlinks">
<roundcube:button command="upload-photo" id="uploadformlink" type="link" label="addphoto" class="disabled" classAct="active" onclick="rcmail_ui.show_popup('uploadform', true);return false" condition="env:photocol" /><br/>
<roundcube:button command="delete-photo" type="link" label="delete" class="disabled" classAct="active" condition="env:photocol" />
</div>
</div>
<roundcube:object name="contactedithead" id="contacthead" size="16" form="editform" />
<div style="clear:both"></div>
<div id="contacttabs">
<roundcube:object name="contacteditform" size="40" textareacols="60" deleteIcon="/images/icons/delete.png" form="editform" />
</div>
<p> <p>
<input type="button" value="<roundcube:label name="cancel" />" class="button" onclick="history.back()" />&nbsp; <input type="button" value="<roundcube:label name="cancel" />" class="button" onclick="history.back()" />&nbsp;
<roundcube:button command="save" type="input" class="button mainaction" label="save" /> <roundcube:button command="save" type="input" class="button mainaction" label="save" />
</p> </p>
</form> </form>
</div> </div>
<script type="text/javascript">rcube_init_tabs('contact-details')</script>
<roundcube:object name="photoUploadForm" id="upload-form" size="30" class="popupmenu" />
<script type="text/javascript">rcube_init_tabs('contacttabs')</script>
</body> </body>
</html> </html>

@ -5,18 +5,34 @@
<roundcube:include file="/includes/links.html" /> <roundcube:include file="/includes/links.html" />
<script type="text/javascript" src="/functions.js"></script> <script type="text/javascript" src="/functions.js"></script>
</head> </head>
<body class="iframe"> <body class="iframe" onload="rcube_init_mail_ui()">
<div id="contact-title" class="boxtitle"><roundcube:label name="editcontact" /></div> <div id="contact-title" class="boxtitle"><roundcube:label name="editcontact" /></div>
<div id="contact-details" class="boxcontent"> <div id="contact-details" class="boxcontent">
<roundcube:object name="contacteditform" size="40" /> <form name="editform" method="post" action="./">
<div id="contactphoto">
<roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" />
<div class="formlinks">
<roundcube:button command="upload-photo" id="uploadformlink" type="link" label="replacephoto" class="disabled" classAct="active" onclick="rcmail_ui.show_popup('uploadform', true);return false" condition="env:photocol" /><br/>
<roundcube:button command="delete-photo" type="link" label="delete" class="disabled" classAct="active" condition="env:photocol" />
</div>
</div>
<roundcube:object name="contactedithead" id="contacthead" size="16" form="editform" />
<div style="clear:both"></div>
<div id="contacttabs">
<roundcube:object name="contacteditform" size="40" textareacols="60" deleteIcon="/images/icons/delete.png" form="editform" />
</div>
<p> <p>
<roundcube:button command="show" type="input" class="button" label="cancel" />&nbsp; <roundcube:button command="show" type="input" class="button" label="cancel" />&nbsp;
<roundcube:button command="save" type="input" class="button mainaction" label="save" /> <roundcube:button command="save" type="input" class="button mainaction" label="save" />
</p> </p>
</form> </form>
</div> </div>
<script type="text/javascript">rcube_init_tabs('contact-details')</script>
<roundcube:object name="photoUploadForm" id="upload-form" size="30" class="popupmenu" />
<script type="text/javascript">rcube_init_tabs('contacttabs')</script>
</body> </body>
</html> </html>

Loading…
Cancel
Save