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
$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
$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)
$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
// leave blank if draft messages should not be stored
$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
'search_base_dn' => '',
'search_filter' => '', // e.g. '(&(objectClass=posixAccount)(uid=%u))'
'writable' => false, // Indicates if we can write to the LDAP directory or not.
// If writable is true then these fields need to be populated:
// 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_version' => 3, // using LDAPv3
'search_fields' => array('mail', 'cn'), // fields to search in
'name_field' => 'cn', // this field represents the contact's name
'email_field' => 'mail', // this field represents the contact's e-mail
'surname_field' => 'sn', // this field represents the contact's last name
'firstname_field' => 'gn', // this field represents the contact's first name
'fieldmap' => array( // mapping of contact fields to directory attributes
// Roundcube => LDAP
'name' => 'cn',
'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.
'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
@ -489,6 +511,10 @@ $rcmail_config['autocomplete_addressbooks'] = array('sql');
// may need to do lengthy results building given overly-broad searches
$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
// ----------------------------------

@ -71,6 +71,9 @@ class html
*/
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');
$suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : '';
@ -147,7 +150,7 @@ class html
$attr = array('href' => $attr);
}
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 $options = array();
protected $allowed = array('name','size','tabindex','autocomplete',
'multiple','onchange','disabled');
'multiple','onchange','disabled','rel');
/**
* 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
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++;
}
@ -819,32 +819,43 @@ function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
* @return string HTML field definition
*/
function rcmail_get_edit_field($col, $value, $attrib, $type='text')
{
{
static $colcounts = array();
$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';
$input = new html_checkbox($attrib);
}
else if ($type=='textarea')
{
}
else if ($type == 'textarea') {
$attrib['cols'] = $attrib['size'];
$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);
}
// use value from post
if (!empty($_POST[$fname]))
$value = get_input_value($fname, RCUBE_INPUT_POST,
$type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
if (isset($_POST[$fname])) {
$postvalue = get_input_value($fname, RCUBE_INPUT_POST,
$type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
$value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
}
$out = $input->show($value);
return $out;
}
}
/**

@ -114,7 +114,7 @@ class rcmail
public $comm_path = './';
private $texts;
private $books = array();
private $address_books = array();
private $action_map = array();
@ -331,6 +331,10 @@ class rcmail
if ($plugin['instance'] instanceof rcube_addressbook) {
$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]) {
$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
if (!in_array($contacts, $this->books))
$this->books[] = $contacts;
if (!isset($this->address_books[$id]))
$this->address_books[$id] = $contacts;
return $contacts;
}
@ -373,11 +377,12 @@ class rcmail
// We are using the DB address book
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(
'id' => 0,
'id' => '0',
'name' => rcube_label('personaladrbook'),
'groups' => $contacts->groups,
'groups' => $this->address_books['0']->groups,
'readonly' => false,
'autocomplete' => in_array('sql', $autocomplete)
);
@ -398,14 +403,15 @@ class rcmail
$plugin = $this->plugins->exec_hook('addressbooks_list', array('sources' => $list));
$list = $plugin['sources'];
if ($writeable && !empty($list)) {
foreach ($list as $idx => $item) {
if ($item['readonly']) {
foreach ($list as $idx => $item) {
// register source for shutdown function
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]);
}
}
}
return $list;
}
@ -1078,9 +1084,12 @@ class rcmail
if (is_object($this->smtp))
$this->smtp->disconnect();
foreach ($this->books as $book)
if (is_object($book))
foreach ($this->address_books as $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();
}
// before closing the database connection, write session data
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
*

@ -5,7 +5,7 @@
| program/include/rcube_addressbook.php |
| |
| 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 |
| |
| PURPOSE: |
@ -27,13 +27,22 @@
*/
abstract class rcube_addressbook
{
/** public properties */
var $primary_key;
var $groups = false;
var $readonly = true;
var $ready = false;
var $list_page = 1;
var $page_size = 10;
/** constants for error reporting **/
const ERROR_READ_ONLY = 1;
const ERROR_NO_CONNECTION = 2;
const ERROR_INCOMPLETE = 3;
const ERROR_SAVING = 4;
/** 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
@ -54,6 +63,16 @@ abstract class rcube_addressbook
*/
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
*
@ -69,9 +88,11 @@ abstract class rcube_addressbook
* @param array List of fields to search in
* @param string Search value
* @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
@ -97,6 +118,27 @@ abstract class rcube_addressbook
*/
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
* Called on script shutdown
@ -129,6 +171,8 @@ abstract class rcube_addressbook
* Create a new contact record
*
* @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
* @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 */
}
/**
* 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
*
* @param mixed Record identifier
* @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
*/
function update($id, $save_cols)
@ -176,9 +241,10 @@ abstract class rcube_addressbook
/**
* 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
*/
function list_groups()
function list_groups($search = null)
{
/* empty for address books don't supporting groups */
return array();
@ -260,5 +326,34 @@ abstract class rcube_addressbook
/* empty for address books don't supporting groups */
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->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->imgdata = !$this->ie;
}
}

@ -47,13 +47,17 @@ class rcube_contacts extends rcube_addressbook
private $table_cols = array('name', 'email', 'firstname', 'surname', 'vcard');
// public properties
var $primary_key = 'contact_id';
var $readonly = false;
var $groups = true;
var $list_page = 1;
var $page_size = 10;
var $group_id = 0;
var $ready = false;
public $primary_key = 'contact_id';
public $readonly = false;
public $groups = true;
public $list_page = 1;
public $page_size = 10;
public $group_id = 0;
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
*
* @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 boolean True to skip the count query (select only)
* @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->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))) {
$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
if (empty($sql_arr['name']))
$sql_arr['name'] = $sql_arr['email'];
$sql_arr['name'] = $sql_arr['email'][0];
$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 to skip the count query (select only)
* @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())
{
@ -345,12 +359,12 @@ class rcube_contacts extends rcube_addressbook
);
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->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)
{
if (is_object($save_data) && is_a($save_data, rcube_result_set))
return $this->insert_recset($save_data, $check);
if (!is_array($save_data))
return false;
$insert_id = $existing = false;
if ($check)
$existing = $this->search('email', $save_data['email'], true, false);
if ($check) {
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();
foreach ($this->table_cols as $col)
if (isset($save_data[$col])) {
$a_insert_cols[] = $this->db->quoteIdentifier($col);
$a_insert_values[] = $this->db->quote($save_data[$col]);
}
foreach ($save_data as $col => $value) {
$a_insert_cols[] = $this->db->quoteIdentifier($col);
$a_insert_values[] = $this->db->quote($value);
}
if (!$existing->count && !empty($a_insert_cols)) {
$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
*
@ -450,11 +458,12 @@ class rcube_contacts extends rcube_addressbook
{
$updated = false;
$write_sql = array();
$record = $this->get_record($id, true);
$save_cols = $this->convert_save_data($save_cols, $record);
foreach ($this->table_cols as $col)
if (isset($save_cols[$col]))
$write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col),
$this->db->quote($save_cols[$col]));
foreach ($save_cols as $col => $value) {
$write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col), $this->db->quote($value));
}
if (!empty($write_sql)) {
$this->db->query(
@ -468,10 +477,61 @@ class rcube_contacts extends rcube_addressbook
);
$updated = $this->db->affected_rows();
$this->result = null; // clear current result (from get_record())
}
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
{
var $conn;
var $prop = array();
var $fieldmap = array();
protected $conn;
protected $prop = array();
protected $fieldmap = array();
var $filter = '';
var $result = null;
var $ldap_result = null;
var $sort_col = '';
var $mail_domain = '';
var $debug = false;
protected $filter = '';
protected $result = null;
protected $ldap_result = null;
protected $sort_col = '';
protected $mail_domain = '';
protected $debug = false;
/** public properties */
var $primary_key = 'ID';
var $readonly = true;
var $list_page = 1;
var $page_size = 10;
var $ready = false;
public $primary_key = 'ID';
public $readonly = true;
public $list_page = 1;
public $page_size = 10;
public $ready = false;
public $coltypes = array();
/**
@ -57,9 +58,37 @@ class rcube_ldap extends rcube_addressbook
{
$this->prop = $p;
foreach ($p as $prop => $value)
if (preg_match('/^(.+)_field$/', $prop, $matches))
$this->fieldmap[$matches[1]] = $this->_attr_name(strtolower($value));
// fieldmap property is given
if (is_array($p['fieldmap'])) {
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
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)))
{
$this->_debug("S: OK");
$this->_debug("S: OK"/* . print_r($rec, true)*/);
$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.
$newentry = array();
$newentry['objectClass'] = $this->prop['LDAP_Object_Classes'];
foreach ($save_cols as $col => $val) {
$fld = $this->_map_field($col);
foreach ($this->fieldmap as $col => $fld) {
$val = $save_cols[$col];
if (is_array($val))
$val = array_filter($val); // remove empty entries
if ($fld && $val) {
// The field does exist, add it to the entry.
$newentry[$fld] = $val;
@ -491,23 +522,29 @@ class rcube_ldap extends rcube_addressbook
} // end foreach
// 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) {
$missing = null;
if (!isset($newentry[$fld])) {
$newentry[$fld] = $newentry[$this->_map_field('email')];
} // end if
} // end foreach
$missing[] = $fld;
}
}
// 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.
$dn = $this->prop['LDAP_rdn'].'='.rcube_ldap::quote_string($newentry[$this->prop['LDAP_rdn']], true)
.','.$this->prop['base_dn'];
$dn = $this->prop['LDAP_rdn'].'='.rcube_ldap::quote_string($newentry[$this->prop['LDAP_rdn']], true).','.$this->prop['base_dn'];
$this->_debug("C: Add [dn: $dn]: ".print_r($newentry, true));
$res = ldap_add($this->conn, $dn, $newentry);
if ($res === FALSE) {
$this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
} // end if
@ -533,8 +570,8 @@ class rcube_ldap extends rcube_addressbook
$newdata = array();
$replacedata = array();
$deletedata = array();
foreach ($save_cols as $col => $val) {
$fld = $this->_map_field($col);
foreach ($this->fieldmap as $col => $fld) {
$val = $save_cols[$col];
if ($fld) {
// The field does exist compare it to the ldap record.
if ($record[$col] != $val) {
@ -566,6 +603,7 @@ class rcube_ldap extends rcube_addressbook
$this->_debug("C: Delete [dn: $dn]: ".print_r($deletedata, true));
if (!ldap_mod_del($this->conn, $dn, $deletedata)) {
$this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
$this->_debug("S: OK");
@ -575,11 +613,11 @@ class rcube_ldap extends rcube_addressbook
// Handle RDN change
if ($replacedata[$this->prop['LDAP_rdn']]) {
$newdn = $this->prop['LDAP_rdn'].'='
.rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true)
.','.$this->prop['base_dn'];
.rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true)
.','.$this->prop['base_dn'];
if ($dn != $newdn) {
$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']]);
}
}
@ -589,7 +627,7 @@ class rcube_ldap extends rcube_addressbook
if (!ldap_mod_replace($this->conn, $dn, $replacedata)) {
$this->_debug("S: ".ldap_error($this->conn));
return false;
}
}
$this->_debug("S: OK");
} // end if
} // end if
@ -599,6 +637,7 @@ class rcube_ldap extends rcube_addressbook
$this->_debug("C: Add [dn: $dn]: ".print_r($newdata, true));
if (!ldap_mod_add($this->conn, $dn, $newdata)) {
$this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
}
$this->_debug("S: OK");
@ -638,6 +677,7 @@ class rcube_ldap extends rcube_addressbook
$res = ldap_delete($this->conn, $dn);
if ($res === FALSE) {
$this->_debug("S: ".ldap_error($this->conn));
$this->set_error(self::ERROR_SAVING, 'errorsaving');
return false;
} // end if
$this->_debug("S: OK");
@ -679,8 +719,6 @@ class rcube_ldap extends rcube_addressbook
*/
private function _ldap2result($rec)
{
global $RCMAIL;
$out = array();
if ($rec['dn'])
@ -688,11 +726,17 @@ class rcube_ldap extends rcube_addressbook
foreach ($this->fieldmap as $rf => $lf)
{
if ($rec[$lf]['count']) {
if ($rf == 'email' && $this->mail_domain && !strpos($rec[$lf][0], '@'))
$out[$rf] = sprintf('%s@%s', $rec[$lf][0], $this->mail_domain);
for ($i=0; $i < $rec[$lf]['count']; $i++) {
if (!($value = $rec[$lf][$i]))
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
$out[$rf] = $rec[$lf][0];
$out[$rf] = $value;
}
}
@ -741,6 +785,10 @@ class rcube_ldap extends rcube_addressbook
*/
function quote_string($str, $dn=false)
{
// take firt entry if array given
if (is_array($str))
$str = reset($str);
if ($dn)
$replace = array(','=>'\2c', '='=>'\3d', '+'=>'\2b', '<'=>'\3c',
'>'=>'\3e', ';'=>'\3b', '\\'=>'\5c', '"'=>'\22', '#'=>'\23');

@ -83,7 +83,20 @@ abstract class rcube_plugin
* Initialization method, needs to be implemented by the plugin itself
*/
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.
* The loaded values are patched over the global configuration.

@ -109,42 +109,9 @@ class rcube_plugin_api
$this->output = $rcmail->output;
$this->config = $rcmail->config;
$plugins_dir = dir($this->dir);
$plugins_dir = unslashify($plugins_dir->path);
$plugins_enabled = (array)$rcmail->config->get('plugins', array());
foreach ($plugins_enabled as $plugin_name) {
$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($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);
}
$this->load_plugin($plugin_name);
}
// check existance of all required core plugins
@ -158,31 +125,14 @@ class rcube_plugin_api
}
// load required core plugin if no derivate was found
if (!$loaded) {
$fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
if (!$loaded)
$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
if (!$loaded) {
raise_error(array('code' => 520, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Requried plugin $plugin_name was not loaded"), true, true);
'file' => __FILE__, 'line' => __LINE__,
'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
}
/**
* 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;
}
/**
* 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.
*

@ -996,8 +996,11 @@ class rcube_template extends rcube_html_page
$attrib['action'] = './';
// 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;
}
else
return $this->form_tag($attrib, $hidden->show() . $content);
}

@ -5,7 +5,7 @@
| program/include/rcube_vcard.php |
| |
| 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 |
| |
| PURPOSE: |
@ -33,6 +33,24 @@ class rcube_vcard
'FN' => 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 $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
*/
@ -113,6 +189,28 @@ class rcube_vcard
{
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 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) {
case 'name':
case 'displayname':
$this->raw['FN'][0][0] = $value;
break;
case 'surname':
$this->raw['N'][0][0] = $value;
break;
case 'firstname':
$this->raw['N'][0][1] = $value;
break;
case 'surname':
$this->raw['N'][0][0] = $value;
case 'middlename':
$this->raw['N'][0][2] = $value;
break;
case 'prefix':
$this->raw['N'][0][3] = $value;
break;
case 'suffix':
$this->raw['N'][0][4] = $value;
break;
case 'nickname':
$this->raw['NICKNAME'][0][0] = $value;
break;
@ -146,13 +260,47 @@ class rcube_vcard
$this->raw['ORG'][0][0] = $value;
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':
$index = $this->get_type_index('EMAIL', $section);
if (!is_array($this->raw['EMAIL'][$index])) {
$this->raw['EMAIL'][$index] = array(0 => $value, 'type' => array('INTERNET', $section, 'pref'));
}
else {
$this->raw['EMAIL'][$index][0] = $value;
$this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type)));
$this->email[] = $value;
break;
case 'im':
// 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;
}

@ -319,7 +319,20 @@ function rcube_webmail()
if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform) {
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) {
this.enable_command('search', 'reset-search', 'moveto', true);
@ -639,6 +652,9 @@ function rcube_webmail()
input_email.focus();
break;
}
// clear empty input fields
$('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
}
this.gui_objects.editform.submit();
@ -996,13 +1012,17 @@ function rcube_webmail()
case 'export':
if (this.contact_list.rowcount > 0) {
var add_url = (this.env.source ? '_source='+urlencode(this.env.source)+'&' : '');
if (this.env.search_request)
add_url += '_search='+this.env.search_request;
this.goto_url('export', add_url);
this.goto_url('export', { _source:this.env.source, _gid:this.env.group, _search:this.env.search_request });
}
break;
case 'upload-photo':
this.upload_contact_photo(props);
break;
case 'delete-photo':
this.replace_contact_photo('-del-');
break;
// user settings commands
case 'preferences':
@ -1158,7 +1178,7 @@ function rcube_webmail()
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
if (send) {
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}, function(e) {
this.async_upload_form(form, 'upload', function(e) {
var d, content = '';
try {
if (this.contentDocument) {
@ -3218,11 +3218,6 @@ function rcube_webmail()
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
var content = this.get_label('uploading');
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 *********/
/*********************************************************/
@ -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
this.set_pagetitle = function(title)
{
@ -4951,6 +5122,39 @@ function rcube_webmail()
/********************************************************/
/********* 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)
{
@ -4965,28 +5169,13 @@ function rcube_webmail()
this.goto_url = function(action, query, lock)
{
var url = this.env.comm_path,
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);
this.redirect(this.url(action, query));
};
// send a http request to the server
this.http_request = function(action, query, lock)
{
var url = this.env.comm_path;
// overwrite task name
if (action.match(/([a-z]+)\/([a-z-_]+)/)) {
action = RegExp.$2;
url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
}
var url = this.url(action, query);
// trigger plugin hook
var result = this.triggerEvent('request'+action, query);
@ -4999,7 +5188,7 @@ function rcube_webmail()
query = result;
}
url += '&_remote=1&_action=' + action + (query ? '&' : '') + query;
url += '&_remote=1';
// send request
console.log('HTTP GET: ' + url);
@ -5013,15 +5202,7 @@ function rcube_webmail()
// send a http POST request to the server
this.http_post = function(action, postdata, lock)
{
var url = this.env.comm_path;
// 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;
var url = this.url(action);
if (postdata && typeof(postdata) == 'object') {
postdata._remote = 1;
@ -5168,6 +5349,37 @@ function rcube_webmail()
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
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.';
// address boook
$labels['name'] = 'Display name';
$labels['firstname'] = 'First name';
$labels['surname'] = 'Last name';
$labels['email'] = 'E-Mail';
$labels['name'] = 'Display name';
$labels['firstname'] = 'First name';
$labels['surname'] = 'Last name';
$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['editcontact'] = 'Edit contact';
$labels['contacts'] = 'Contacts';
@ -258,6 +285,8 @@ $labels['cancel'] = 'Cancel';
$labels['save'] = 'Save';
$labels['delete'] = 'Delete';
$labels['rename'] = 'Rename';
$labels['addphoto'] = 'Add';
$labels['replacephoto'] = 'Replace';
$labels['newcontact'] = 'Create new contact card';
$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['addresswriterror'] = 'The selected address book is not writeable';
$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['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>';
@ -137,5 +137,6 @@ $messages['namecannotbeempty'] = 'Name cannot be empty';
$messages['nametoolong'] = 'Name is too long';
$messages['folderupdated'] = 'Folder updated 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));
if (!$plugin['abort']) {
if ($insert_id = $TARGET->insert($a_record, false)) {
if ($insert_id = $TARGET->insert($plugin['record'], false)) {
$ids[] = $insert_id;
$success++;
}

@ -38,6 +38,10 @@ if ($OUTPUT->ajax_call &&
// count contacts for this user
$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
$OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size));
$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)
{
global $RCMAIL, $CONTACTS, $OUTPUT;
global $RCMAIL, $CONTACTS, $CONTACT_COLTYPES;
// check if we have a valid result
if ($RCMAIL->action != 'add'
&& !(($result = $CONTACTS->get_result()) && ($record = $result->first()))
) {
$OUTPUT->show_message('contactnotfound');
$RCMAIL->output->show_message('contactnotfound');
return false;
}
// add some labels to client
$OUTPUT->add_label('noemailwarning', 'nonamewarning');
$RCMAIL->output->add_label('noemailwarning', 'nonamewarning');
$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;
$form = array(
'info' => array(
'name' => rcube_label('contactproperties'),
'content' => array(
'name' => array('type' => 'text', 'size' => $i_size),
'firstname' => array('type' => 'text', 'size' => $i_size),
'surname' => array('type' => 'text', 'size' => $i_size),
'email' => array('type' => 'text', 'size' => $i_size),
'gender' => array('visible' => false),
'maidenname' => array('size' => $i_size),
'email' => array('size' => $i_size, 'visible' => true),
'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);
unset($attrib['form']);
// 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;
}
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
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('contactphoto', 'rcmail_contact_photo');
$OUTPUT->add_handler('photouploadform', 'rcmail_upload_photo_form');
if (!$CONTACTS->get_result() && $OUTPUT->template_exists('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"');
while ($result && ($row = $result->next())) {
$vcard = new rcube_vcard($row['vcard']);
$vcard->set('displayname', $row['name']);
$vcard->set('firstname', $row['firstname']);
$vcard->set('surname', $row['surname']);
$vcard->set('email', $row['email']);
echo $vcard->export();
// we already have a vcard record
if ($row['vcard']) {
echo $row['vcard'];
}
// copy values into vcard object
else {
$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;

@ -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)
{
global $RCMAIL, $OUTPUT;
@ -72,23 +122,21 @@ function rcmail_directory_list($attrib)
html::a(array('href' => '%s',
'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s'));
if (!$current && strtolower($RCMAIL->config->get('address_book_type', 'sql')) != 'ldap') {
$current = '0';
}
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()));
}
// currently selected is the first address source in the list
if (!isset($current))
$current = strval(key((array)$OUTPUT->env['address_sources']));
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);
$dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id);
$out .= sprintf($line_templ, $dom_id, ($current == $id ? 'selected' : ''),
$dom_id = preg_replace('/[^a-z0-9\-_]/i', '_', $id);
$out .= sprintf($line_templ, $dom_id, ($current === $id ? 'selected' : ''),
Q(rcmail_url(null, array('_source' => $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'];
$out = $groupdata['out'];
}
@ -130,16 +178,16 @@ function rcmail_contacts_list($attrib)
{
global $CONTACTS, $OUTPUT;
// define list of cols to be displayed
$a_show_cols = array('name');
// 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
if (!strlen($attrib['id']))
$attrib['id'] = 'rcmAddressList';
// define list of cols to be displayed
$a_show_cols = array('name');
// create XHTML table
$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
$plugin = $RCMAIL->plugins->exec_hook('contact_form', array(
@ -243,35 +291,222 @@ function rcmail_contact_form($form, $record)
$form = $plugin['form'];
$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 = '';
// 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']))
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 = '';
if (is_array($fieldset['content'])) {
$table = new html_table(array('cols' => 2));
foreach ($fieldset['content'] as $col => $colprop) {
$colprop['id'] = 'rcmfd_'.$col;
$label = !empty($colprop['label']) ? $colprop['label'] : rcube_label($col);
// remove subtype part of col name
list($field, $subtype) = explode(':', $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'])) {
$value = $colprop['value'];
}
else if ($RCMAIL->action == 'show') {
$value = $record[$col];
$values = (array)$colprop['value'];
}
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)));
$table->add(null, $value);
// hack: create empty values array to force this field to be displayed
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 {
$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";
}
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;
}
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
$OUTPUT->add_handlers(array(
'directorylist' => 'rcmail_directory_list',
@ -297,6 +574,8 @@ $OUTPUT->add_handlers(array(
// register action aliases
$RCMAIL->register_action_map(array(
'add' => 'edit.inc',
'photo' => 'show.inc',
'upload-photo' => 'save.inc',
'group-create' => 'groups.inc',
'group-rename' => 'groups.inc',
'group-delete' => 'groups.inc',

@ -79,8 +79,7 @@ else if ($RCMAIL->action == 'group-create') {
if ($created && $OUTPUT->ajax_call) {
$OUTPUT->show_message('groupcreated', 'confirmation');
$OUTPUT->command('insert_contact_group', array(
'source' => $source, 'id' => $created['id'], 'name' => $created['name']));
$OUTPUT->command('insert_contact_group', array('source' => $source) + $created);
}
else if (!$created) {
$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(
'name' => $vcard->displayname,
'firstname' => $vcard->firstname,
'surname' => $vcard->surname,
'email' => $email,
'vcard' => $vcard->export(),
);
$a_record = $vcard->get_assoc();
$a_record['vcard'] = $vcard->export();
$plugin = $RCMAIL->plugins->exec_hook('contact_create', array('record' => $a_record, 'source' => null));
$a_record = $plugin['record'];

@ -20,7 +20,7 @@
*/
// get contacts for this user
$result = $CONTACTS->list_records();
$result = $CONTACTS->list_records(array('name'));
// update message count display
$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);
$recipients = $CONTACTS->search($CONTACTS->primary_key, $cid);
while (is_object($recipients) && ($rec = $recipients->iterate()))
$mailto[] = format_email_recipient($rec['email'], $rec['name']);
while (is_object($recipients) && ($rec = $recipients->iterate())) {
$emails = $CONTACTS->get_col_values('email', $rec, true);
$mailto[] = format_email_recipient($emails[0], $rec['name']);
}
}
if (!empty($mailto))

@ -5,7 +5,7 @@
| program/steps/addressbook/save.inc |
| |
| 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 |
| |
| PURPOSE: |
@ -29,33 +29,147 @@ if ($CONTACTS->readonly) {
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
foreach ($a_save_cols as $col) {
$a_record = array();
foreach ($GLOBALS['CONTACT_COLTYPES'] as $col => $colprop) {
$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);
}
}
// Validity checks
$_email = idn_to_ascii($a_record['email']);
if (!check_email($_email, false)) {
$OUTPUT->show_message('emailformaterror', 'warning', array('email' => $_email));
if (empty($a_record['name']))
$a_record['name'] = join(' ', array_filter(array($a_record['prefix'], $a_record['firstname'], $a_record['middlename'], $a_record['surname'], $a_record['suffix'],)));
#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);
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
if (!empty($cid))
{
@ -92,7 +206,8 @@ if (!empty($cid))
}
else {
// 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');
}
}
@ -100,10 +215,16 @@ if (!empty($cid))
// insert a new contact
else {
// 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
if ($existing->count) {
if ($existing) {
$OUTPUT->show_message('contactexists', 'warning', null, false);
rcmail_overwrite_action('add');
return;
@ -138,7 +259,8 @@ else {
}
else {
// 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');
}
}

@ -5,7 +5,7 @@
| program/steps/addressbook/search.inc |
| |
| 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 |
| |
| PURPOSE: |
@ -28,11 +28,11 @@ $search_request = md5('addr'.$search);
// get contacts for this user
$result = $CONTACTS->search(array('name','email'), $search);
// save search settings in session
$_SESSION['search'][$search_request] = $CONTACTS->get_search_set();
if ($result->count > 0)
{
// save search settings in session
$_SESSION['search'][$search_request] = $CONTACTS->get_search_set();
// create javascript list
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']);
}
// 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;
@ -36,54 +58,99 @@ function rcmail_contact_details($attrib)
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');
$form = array(
'info' => array(
'name' => rcube_label('contactproperties'),
'head' => array( // section 'head' is magic!
'content' => array(
'name' => array('type' => 'text', 'size' => $i_size),
'firstname' => array('type' => 'text', 'size' => $i_size),
'surname' => array('type' => 'text', 'size' => $i_size),
'email' => array('type' => 'text', 'size' => $i_size),
'prefix' => array('type' => 'text'),
'firstname' => array('type' => 'text'),
'middlename' => array('type' => 'text'),
'surname' => array('type' => 'text'),
'suffix' => array('type' => 'text'),
),
),
'groups' => array(
'name' => rcube_label('groups'),
'content' => '',
),
);
// Get content of groups fieldset
if ($groups = rcmail_contact_record_groups($record['ID'])) {
$form['groups']['content'] = $groups;
}
else {
unset($form['groups']);
unset($attrib['name']);
return rcmail_contact_form($form, $record, $attrib);
}
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'])) {
$form['info']['content']['email']['value'] = html::a(array(
'href' => 'mailto:' . $record['email'],
'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($record['email'])),
'title' => rcube_label('composeto'),
'class' => $microformats['email'],
), Q($record['email']));
$i_size = !empty($attrib['size']) ? $attrib['size'] : 40;
$form = array(
'info' => array(
'name' => rcube_label('contactproperties'),
'content' => array(
'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]) {
$form['info']['content'][$col]['value'] = html::span($microformats[$col], Q($record[$col]));
}
if ($CONTACTS->groups) {
$form['groups'] = array(
'name' => rcube_label('groups'),
'content' => rcmail_contact_record_groups($record['ID']),
);
}
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)
{
global $RCMAIL, $CONTACTS, $GROUPS;
@ -124,6 +191,8 @@ function rcmail_contact_record_groups($contact_id)
//$OUTPUT->framed = $_framed;
$OUTPUT->add_handler('contacthead', 'rcmail_contact_head');
$OUTPUT->add_handler('contactdetails', 'rcmail_contact_details');
$OUTPUT->add_handler('contactphoto', 'rcmail_contact_photo');
$OUTPUT->send('contact');

@ -29,8 +29,10 @@ if ($RCMAIL->action == 'group-expand') {
$abook->set_group($gid);
$abook->set_pagesize(1000); // TODO: limit number of group members by config
$result = $abook->list_records(array('email','name'));
while ($result && ($sql_arr = $result->iterate()))
$members[] = format_email_recipient($sql_arr['email'], $sql_arr['name']);
while ($result && ($sql_arr = $result->iterate())) {
foreach ((array)$sql_arr['email'] as $email)
$members[] = format_email_recipient($email, $sql_arr['name']);
}
$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')) {
while ($sql_arr = $result->iterate()) {
$contact = format_email_recipient($sql_arr['email'], $sql_arr['name']);
// when we've got more than one book, we need to skip duplicates
if ($books_num == 1 || !in_array($contact, $contacts)) {
$contacts[] = $contact;
if (count($contacts) >= $MAXNUM)
break 2;
foreach ((array)$abook->get_col_values('email', $sql_arr, true) as $email) {
$contact = format_email_recipient($email, $sql_arr['name']);
// when we've got more than one book, we need to skip duplicates
if ($books_num == 1 || !in_array($contact, $contacts)) {
$contacts[] = $contact;
if (count($contacts) >= $MAXNUM)
break 2;
}
}
}
}

@ -1133,7 +1133,7 @@ function rcmail_compose_attachment_form($attrib)
$out = html::div($attrib,
$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('buttons',
$button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' .

@ -214,3 +214,166 @@ body.iframe,
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;
}
input.placeholder,
textarea.placeholder
{
color: #aaa;
}
input.button
{
height: 20px;
@ -114,6 +120,20 @@ img
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 */
#mainscreen

@ -25,9 +25,8 @@ function rcube_show_advanced(visible)
// Warning: don't place "caller" <script> inside page element (id)
function rcube_init_tabs(id, current)
{
var content = document.getElementById(id),
// get fieldsets of the higher-level (skip nested fieldsets)
fs = $('fieldset', content).not('fieldset > fieldset');
var content = $('#'+id),
fs = content.children('fieldset');
if (!fs.length)
return;
@ -42,9 +41,7 @@ function rcube_init_tabs(id, current)
// convert fildsets into tabs
fs.each(function(idx) {
var tab, a, elm = $(this),
// get first legend element
legend = $(elm).children('legend');
var tab, a, elm = $(this), legend = elm.children('legend');
// create a tab
a = $('<a>').text(legend.text()).attr('href', '#');
@ -66,8 +63,7 @@ function rcube_init_tabs(id, current)
function rcube_show_tab(id, index)
{
var content = document.getElementById(id),
fs = $('fieldset', content).not('fieldset > fieldset');
var fs = $('#'+id).children('fieldset');
fs.each(function(idx) {
// Show/hide fieldset (tab content)
@ -94,7 +90,8 @@ function rcube_mail_ui()
mailboxmenu: {id:'mailboxoptionsmenu', above:1},
composemenu: {id:'composeoptionsmenu', editable:1},
// 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;
@ -136,6 +133,9 @@ show_popupmenu: function(popup, show)
if (!above && pos.top + ref.offsetHeight + obj.height() > window.innerHeight)
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)) });
}
@ -500,6 +500,9 @@ function rcube_init_mail_ui()
if (rcmail.env.action == 'compose')
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)

@ -236,3 +236,9 @@ table.records-table thead tr td
{
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;
}
.formlinks a,
.formlinks a:visited
{
color: #999999;
font-size: 11px;
text-decoration: none;
}
.formlinks a,
.formlinks a:visited
{
color: #CC0000;
}
#compose-editorfooter
{
position: absolute;

@ -9,12 +9,17 @@
<div id="contact-title" class="boxtitle"><roundcube:label name="contactproperties" /></div>
<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>
<roundcube:button command="edit" type="input" class="button" label="editcontact" condition="!ENV:readonly" />
</p>
</div>
<script type="text/javascript">rcube_init_tabs('contact-details')</script>
<script type="text/javascript">rcube_init_tabs('contacttabs')</script>
</body>
</html>

@ -5,18 +5,34 @@
<roundcube:include file="/includes/links.html" />
<script type="text/javascript" src="/functions.js"></script>
</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-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>
<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" />
</p>
</form>
</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>
</html>

@ -5,18 +5,34 @@
<roundcube:include file="/includes/links.html" />
<script type="text/javascript" src="/functions.js"></script>
</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-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>
<roundcube:button command="show" type="input" class="button" label="cancel" />&nbsp;
<roundcube:button command="save" type="input" class="button mainaction" label="save" />
</p>
</form>
</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>
</html>

Loading…
Cancel
Save