summaryrefslogtreecommitdiff
path: root/program/include
diff options
context:
space:
mode:
authorthomascube <thomas@roundcube.net>2011-01-18 18:00:57 +0000
committerthomascube <thomas@roundcube.net>2011-01-18 18:00:57 +0000
commit0501b637a3177cce441166b5fcfe27c9bd9fbe0f (patch)
tree5460128ef65d2510a7538c4a0e7336987e090ca1 /program/include
parente81a30752b244394b03cbcaa0df254c93b379782 (diff)
Merge branch devel-addressbook (r4193:4382) back into trunk
Diffstat (limited to 'program/include')
-rw-r--r--program/include/html.php7
-rw-r--r--program/include/main.inc41
-rw-r--r--program/include/rcmail.php143
-rw-r--r--program/include/rcube_addressbook.php117
-rw-r--r--program/include/rcube_browser.php1
-rw-r--r--program/include/rcube_contacts.php140
-rw-r--r--program/include/rcube_ldap.php126
-rw-r--r--program/include/rcube_plugin.php15
-rw-r--r--program/include/rcube_plugin_api.php118
-rw-r--r--program/include/rcube_shared.inc19
-rwxr-xr-xprogram/include/rcube_template.php5
-rw-r--r--program/include/rcube_vcard.php172
12 files changed, 714 insertions, 190 deletions
diff --git a/program/include/html.php b/program/include/html.php
index a7599cd9f..ef7314e6f 100644
--- a/program/include/html.php
+++ b/program/include/html.php
@@ -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
diff --git a/program/include/main.inc b/program/include/main.inc
index 1ddb5f9c4..0815c259f 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -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;
- }
+}
/**
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index cdf959f2e..56181a733 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -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'])
@@ -1307,6 +1316,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
*
* @param string Cookie name
diff --git a/program/include/rcube_addressbook.php b/program/include/rcube_addressbook.php
index ae2a286c3..b9a31cc74 100644
--- a/program/include/rcube_addressbook.php
+++ b/program/include/rcube_addressbook.php
@@ -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
@@ -55,6 +64,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
*
* @param array List of cols to show
@@ -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
@@ -98,6 +119,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
*/
@@ -138,10 +182,31 @@ abstract class rcube_addressbook
}
/**
+ * 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;
+ }
+
}
diff --git a/program/include/rcube_browser.php b/program/include/rcube_browser.php
index 581284ab7..c5f1b8a7a 100644
--- a/program/include/rcube_browser.php
+++ b/program/include/rcube_browser.php
@@ -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;
}
}
diff --git a/program/include/rcube_contacts.php b/program/include/rcube_contacts.php
index 9761e687a..4a4c1e27e 100644
--- a/program/include/rcube_contacts.php
+++ b/program/include/rcube_contacts.php
@@ -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(
@@ -426,20 +448,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
*
* @param mixed Record identifier
@@ -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;
+ }
/**
diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php
index 17d049ef1..7ea22ebac 100644
--- a/program/include/rcube_ldap.php
+++ b/program/include/rcube_ldap.php
@@ -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');
diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php
index 85ed77492..09791374c 100644
--- a/program/include/rcube_plugin.php
+++ b/program/include/rcube_plugin.php
@@ -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.
diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php
index 0f7ab3f4b..54a9a8bee 100644
--- a/program/include/rcube_plugin_api.php
+++ b/program/include/rcube_plugin_api.php
@@ -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;
+ }
/**
diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc
index 36c832e80..83eefd6da 100644
--- a/program/include/rcube_shared.inc
+++ b/program/include/rcube_shared.inc
@@ -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.
*
diff --git a/program/include/rcube_template.php b/program/include/rcube_template.php
index 2aa16f098..1d1a95b90 100755
--- a/program/include/rcube_template.php
+++ b/program/include/rcube_template.php
@@ -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);
}
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
index 70ea55c87..11872249c 100644
--- a/program/include/rcube_vcard.php
+++ b/program/include/rcube_vcard.php
@@ -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;
@@ -107,12 +125,92 @@ 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
*/
public function export()
{
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;
}