Changeset 4424 in subversion


Ignore:
Timestamp:
Jan 18, 2011 1:00:57 PM (2 years ago)
Author:
thomasb
Message:

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

Location:
trunk/roundcubemail
Files:
39 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/roundcubemail

  • trunk/roundcubemail/config/main.inc.php.dist

    r4410 r4424  
    309309$rcmail_config['mime_magic'] = '/usr/share/misc/magic'; 
    310310 
     311// path to imagemagick identify binary 
     312$rcmail_config['im_identify_path'] = null; 
     313 
     314// path to imagemagick convert binary 
     315$rcmail_config['im_convert_path'] = null; 
     316 
     317// maximum size of uploaded contact photos in pixel 
     318$rcmail_config['contact_photo_size'] = 160; 
     319 
    311320// Enable DNS checking for e-mail address validation 
    312321$rcmail_config['email_dns_check'] = false; 
     
    346355// use this format for today's date display (date or strftime format) 
    347356$rcmail_config['date_today'] = 'H:i'; 
     357 
     358// use this format for date display without time (date or strftime format) 
     359$rcmail_config['date_format'] = 'Y-m-d'; 
    348360 
    349361// store draft message is this mailbox 
     
    459471  'search_base_dn' => '', 
    460472  'search_filter'  => '',   // e.g. '(&(objectClass=posixAccount)(uid=%u))' 
    461  
    462473  'writable'      => false,   // Indicates if we can write to the LDAP directory or not. 
    463474  // If writable is true then these fields need to be populated: 
     
    468479  'ldap_version'  => 3,       // using LDAPv3 
    469480  'search_fields' => array('mail', 'cn'),  // fields to search in 
    470   'name_field'    => 'cn',    // this field represents the contact's name 
    471   'email_field'   => 'mail',  // this field represents the contact's e-mail 
    472   'surname_field' => 'sn',    // this field represents the contact's last name 
    473   'firstname_field' => 'gn',  // this field represents the contact's first name 
     481  'fieldmap' => array(      // mapping of contact fields to directory attributes 
     482    // Roundcube  => LDAP 
     483    'name'        => 'cn', 
     484    'surname'     => 'sn', 
     485    'firstname'   => 'givenName', 
     486    'email'       => 'mail', 
     487    'phone:home'  => 'homePhone', 
     488    'phone:work'  => 'telephoneNumber', 
     489    'phone:mobile' => 'mobile', 
     490    'street'      => 'street', 
     491    'zipcode'     => 'postalCode', 
     492    'locality'    => 'l', 
     493    'country'     => 'c', 
     494    'organization' => 'o', 
     495  ), 
    474496  'sort'          => 'cn',    // The field to sort the listing by. 
    475497  'scope'         => 'sub',   // search mode: sub|base|list 
     
    490512$rcmail_config['autocomplete_min_length'] = 1; 
    491513 
     514// show address fields in this order 
     515// available placeholders: {street}, {locality}, {zipcode}, {country}, {region} 
     516$rcmail_config['address_template'] = '{street}<br/>{locality} {zipcode}<br/>{country} {region}'; 
     517 
    492518// ---------------------------------- 
    493519// USER PREFERENCES 
  • trunk/roundcubemail/program/include/html.php

    r4411 r4424  
    7272    public static function tag($tagname, $attrib = array(), $content = null, $allowed_attrib = null) 
    7373    { 
     74        if (is_string($attrib)) 
     75            $attrib = array('class' => $attrib); 
     76 
    7477        $inline_tags = array('a','span','img'); 
    7578        $suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : ''; 
     
    148151        } 
    149152        return self::tag('a', $attr, $cont, array_merge(self::$common_attrib, 
    150             array('href','target','name','onclick','onmouseover','onmouseout','onmousedown','onmouseup'))); 
     153            array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup'))); 
    151154    } 
    152155 
     
    502505    protected $options = array(); 
    503506    protected $allowed = array('name','size','tabindex','autocomplete', 
    504         'multiple','onchange','disabled'); 
     507        'multiple','onchange','disabled','rel'); 
    505508     
    506509    /** 
  • trunk/roundcubemail/program/include/main.inc

    r4420 r4424  
    800800      // format each col 
    801801      foreach ($a_show_cols as $col) 
    802         $table->add($col, Q($row_data[$col])); 
     802        $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col])); 
    803803         
    804804      $c++; 
     
    820820 */ 
    821821function rcmail_get_edit_field($col, $value, $attrib, $type='text') 
    822   { 
     822{ 
     823  static $colcounts = array(); 
     824   
    823825  $fname = '_'.$col; 
    824   $attrib['name'] = $fname; 
    825    
    826   if ($type=='checkbox') 
    827     { 
     826  $attrib['name'] = $fname . ($attrib['array'] ? '[]' : ''); 
     827  $attrib['class'] = trim($attrib['class'] . ' ff_' . $col); 
     828   
     829  if ($type == 'checkbox') { 
    828830    $attrib['value'] = '1'; 
    829831    $input = new html_checkbox($attrib); 
    830     } 
    831   else if ($type=='textarea') 
    832     { 
     832  } 
     833  else if ($type == 'textarea') { 
    833834    $attrib['cols'] = $attrib['size']; 
    834835    $input = new html_textarea($attrib); 
    835     } 
    836   else 
     836  } 
     837  else if ($type == 'select') { 
     838    $input = new html_select($attrib); 
     839    $input->add('---', ''); 
     840    $input->add(array_values($attrib['options']), array_keys($attrib['options'])); 
     841  } 
     842  else { 
     843    if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden') 
     844        $attrib['type'] = 'text'; 
    837845    $input = new html_inputfield($attrib); 
     846  } 
    838847 
    839848  // use value from post 
    840   if (!empty($_POST[$fname])) 
    841     $value = get_input_value($fname, RCUBE_INPUT_POST, 
    842             $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false); 
     849  if (isset($_POST[$fname])) { 
     850    $postvalue = get_input_value($fname, RCUBE_INPUT_POST, 
     851      $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false); 
     852    $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue; 
     853  } 
    843854 
    844855  $out = $input->show($value); 
    845           
     856 
    846857  return $out; 
    847   } 
     858} 
    848859 
    849860 
  • trunk/roundcubemail/program/include/rcmail.php

    r4420 r4424  
    115115 
    116116  private $texts; 
    117   private $books = array(); 
     117  private $address_books = array(); 
    118118  private $action_map = array(); 
    119119 
     
    331331    if ($plugin['instance'] instanceof rcube_addressbook) { 
    332332      $contacts = $plugin['instance']; 
     333    } 
     334    // use existing instance 
     335    else if (isset($this->address_books[$id]) && is_a($this->address_books[$id], 'rcube_addressbook') && (!$writeable || !$this->address_books[$id]->readonly)) { 
     336      $contacts = $this->address_books[$id]; 
    333337    } 
    334338    else if ($id && $ldap_config[$id]) { 
     
    352356 
    353357    // add to the 'books' array for shutdown function 
    354     if (!in_array($contacts, $this->books)) 
    355       $this->books[] = $contacts; 
     358    if (!isset($this->address_books[$id])) 
     359      $this->address_books[$id] = $contacts; 
    356360 
    357361    return $contacts; 
     
    374378    // We are using the DB address book 
    375379    if ($abook_type != 'ldap') { 
    376       $contacts = new rcube_contacts($this->db, null); 
     380      if (!isset($this->address_books['0'])) 
     381        $this->address_books['0'] = new rcube_contacts($this->db, $this->user->ID); 
    377382      $list['0'] = array( 
    378         'id' => 0, 
     383        'id' => '0', 
    379384        'name' => rcube_label('personaladrbook'), 
    380         'groups' => $contacts->groups, 
     385        'groups' => $this->address_books['0']->groups, 
    381386        'readonly' => false, 
    382387        'autocomplete' => in_array('sql', $autocomplete) 
     
    399404    $list = $plugin['sources']; 
    400405 
    401     if ($writeable && !empty($list)) { 
    402       foreach ($list as $idx => $item) { 
    403         if ($item['readonly']) { 
     406    foreach ($list as $idx => $item) { 
     407      // register source for shutdown function 
     408      if (!is_object($this->address_books[$item['id']])) 
     409        $this->address_books[$item['id']] = $item; 
     410      // remove from list if not writeable as requested 
     411      if ($writeable && $item['readonly']) 
    404412          unset($list[$idx]); 
    405         } 
    406       } 
    407     } 
    408  
     413    } 
     414     
    409415    return $list; 
    410416  } 
     
    10791085      $this->smtp->disconnect(); 
    10801086 
    1081     foreach ($this->books as $book) 
    1082       if (is_object($book)) 
     1087    foreach ($this->address_books as $book) { 
     1088      if (!is_object($book))  // maybe an address book instance wasn't fetched using get_address_book() yet 
     1089        $book = $this->get_address_book($book['id']); 
     1090      if (is_a($book, 'rcube_addressbook')) 
    10831091        $book->close(); 
     1092    } 
    10841093 
    10851094    // before closing the database connection, write session data 
     
    13081317 
    13091318  /** 
     1319   * Use imagemagick or GD lib to read image properties 
     1320   * 
     1321   * @param string Absolute file path 
     1322   * @return mixed Hash array with image props like type, width, height or False on error 
     1323   */ 
     1324  public static function imageprops($filepath) 
     1325  { 
     1326    $rcmail = rcmail::get_instance(); 
     1327    if ($cmd = $rcmail->config->get('im_identify_path', false)) { 
     1328      list(, $type, $size) = explode(' ', strtolower(rcmail::exec($cmd. ' 2>/dev/null {in}', array('in' => $filepath)))); 
     1329      if ($size) 
     1330        list($width, $height) = explode('x', $size); 
     1331    } 
     1332    else if (function_exists('getimagesize')) { 
     1333      $imsize = @getimagesize($filepath); 
     1334      $width = $imsize[0]; 
     1335      $height = $imsize[1]; 
     1336      $type = preg_replace('!image/!', '', $imsize['mime']); 
     1337    } 
     1338 
     1339    return $type ? array('type' => $type, 'width' => $width, 'height' => $height) : false; 
     1340  } 
     1341 
     1342 
     1343  /** 
     1344   * Convert an image to a given size and type using imagemagick (ensures input is an image) 
     1345   * 
     1346   * @param $p['in']  Input filename (mandatory) 
     1347   * @param $p['out'] Output filename (mandatory) 
     1348   * @param $p['size']  Width x height of resulting image, e.g. "160x60" 
     1349   * @param $p['type']  Output file type, e.g. "jpg" 
     1350   * @param $p['-opts'] Custom command line options to ImageMagick convert 
     1351   * @return Success of convert as true/false 
     1352   */ 
     1353  public static function imageconvert($p) 
     1354  { 
     1355    $result = false; 
     1356    $rcmail = rcmail::get_instance(); 
     1357    $convert  = $rcmail->config->get('im_convert_path', false); 
     1358    $identify = $rcmail->config->get('im_identify_path', false); 
     1359     
     1360    // imagemagick is required for this 
     1361    if (!$convert) 
     1362        return false; 
     1363 
     1364    if (!(($imagetype = @exif_imagetype($p['in'])) && ($type = image_type_to_extension($imagetype, false)))) 
     1365      list(, $type) = explode(' ', strtolower(rcmail::exec($identify . ' 2>/dev/null {in}', $p))); # for things like eps 
     1366 
     1367    $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps")); 
     1368    $p += array('type' => $type, 'types' => "bmp,eps,gif,jp2,jpg,png,svg,tif", 'quality' => 75); 
     1369    $p['-opts'] = array('-resize' => $p['size'].'>') + (array)$p['-opts']; 
     1370 
     1371    if (in_array($type, explode(',', $p['types']))) # Valid type? 
     1372      $result = rcmail::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {in} {type}:{out}', $p) === ""; 
     1373 
     1374    return $result; 
     1375  } 
     1376 
     1377 
     1378  /** 
     1379   * Construct shell command, execute it and return output as string. 
     1380   * Keywords {keyword} are replaced with arguments 
     1381   * 
     1382   * @param $cmd Format string with {keywords} to be replaced 
     1383   * @param $values (zero, one or more arrays can be passed) 
     1384   * @return output of command. shell errors not detectable 
     1385   */ 
     1386  public static function exec(/* $cmd, $values1 = array(), ... */) 
     1387  { 
     1388    $args = func_get_args(); 
     1389    $cmd = array_shift($args); 
     1390    $values = $replacements = array(); 
     1391 
     1392    // merge values into one array 
     1393    foreach ($args as $arg) 
     1394      $values += (array)$arg; 
     1395 
     1396    preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER); 
     1397    foreach ($matches as $tags) { 
     1398      list(, $tag, $option, $key) = $tags; 
     1399      $parts = array(); 
     1400 
     1401      if ($option) { 
     1402        foreach ((array)$values["-$key"] as $key => $value) { 
     1403          if ($value === true || $value === false || $value === null) 
     1404            $parts[] = $value ? $key : ""; 
     1405          else foreach ((array)$value as $val) 
     1406            $parts[] = "$key " . escapeshellarg($val); 
     1407        } 
     1408      } 
     1409      else { 
     1410        foreach ((array)$values[$key] as $value) 
     1411          $parts[] = escapeshellarg($value); 
     1412      } 
     1413 
     1414      $replacements[$tag] = join(" ", $parts); 
     1415    } 
     1416 
     1417    // use strtr behaviour of going through source string once 
     1418    $cmd = strtr($cmd, $replacements); 
     1419     
     1420    return (string)shell_exec($cmd); 
     1421  } 
     1422 
     1423 
     1424  /** 
    13101425   * Helper method to set a cookie with the current path and host settings 
    13111426   * 
  • trunk/roundcubemail/program/include/rcube_addressbook.php

    r4410 r4424  
    66 |                                                                       | 
    77 | This file is part of the Roundcube Webmail client                     | 
    8  | Copyright (C) 2006-2009, The Roundcube Dev Team                       | 
     8 | Copyright (C) 2006-2011, The Roundcube Dev Team                       | 
    99 | Licensed under the GNU GPL                                            | 
    1010 |                                                                       | 
     
    2828abstract class rcube_addressbook 
    2929{ 
    30     /** public properties */ 
    31     var $primary_key; 
    32     var $groups = false; 
    33     var $readonly = true; 
    34     var $ready = false; 
    35     var $list_page = 1; 
    36     var $page_size = 10; 
     30    /** constants for error reporting **/ 
     31    const ERROR_READ_ONLY = 1; 
     32    const ERROR_NO_CONNECTION = 2; 
     33    const ERROR_INCOMPLETE = 3; 
     34    const ERROR_SAVING = 4; 
     35     
     36    /** public properties (mandatory) */ 
     37    public $primary_key; 
     38    public $groups = false; 
     39    public $readonly = true; 
     40    public $ready = false; 
     41    public $list_page = 1; 
     42    public $page_size = 10; 
     43    public $coltypes = array('name' => array('limit'=>1), 'firstname' => array('limit'=>1), 'surname' => array('limit'=>1), 'email' => array('limit'=>1)); 
     44     
     45    protected $error; 
    3746 
    3847    /** 
     
    5463     */ 
    5564    abstract function reset(); 
     65 
     66    /** 
     67     * Refresh saved search set after data has changed 
     68     * 
     69     * @return mixed New search set 
     70     */ 
     71    function refresh_search() 
     72    { 
     73        return $this->get_search_set(); 
     74    } 
    5675 
    5776    /** 
     
    7089     * @param string  Search value 
    7190     * @param boolean True if results are requested, False if count only 
    72      * @return Indexed list of contact records and 'count' value 
    73      */ 
    74     abstract function search($fields, $value, $strict=false, $select=true); 
     91     * @param boolean True to skip the count query (select only) 
     92     * @param array   List of fields that cannot be empty 
     93     * @return object rcube_result_set List of contact records and 'count' value 
     94     */ 
     95    abstract function search($fields, $value, $strict=false, $select=true, $nocount=false, $required=array()); 
    7596 
    7697    /** 
     
    99120 
    100121    /** 
     122     * Returns the last error occured (e.g. when updating/inserting failed) 
     123     * 
     124     * @return array Hash array with the following fields: type, message 
     125     */ 
     126    function get_error() 
     127    { 
     128      return $this->error; 
     129    } 
     130     
     131    /** 
     132     * Setter for errors for internal use 
     133     * 
     134     * @param int Error type (one of this class' error constants) 
     135     * @param string Error message (name of a text label) 
     136     */ 
     137    protected function set_error($type, $message) 
     138    { 
     139      $this->error = array('type' => $type, 'message' => $message); 
     140    } 
     141 
     142    /** 
    101143     * Close connection to source 
    102144     * Called on script shutdown 
     
    130172     * 
    131173     * @param array Assoziative array with save data 
     174     *  Keys:   Field name with optional section in the form FIELD:SECTION 
     175     *  Values: Field value. Can be either a string or an array of strings for multiple values 
    132176     * @param boolean True to check for duplicates first 
    133177     * @return mixed The created record ID on success, False on error 
     
    139183 
    140184    /** 
     185     * Create new contact records for every item in the record set 
     186     * 
     187     * @param object rcube_result_set Recordset to insert 
     188     * @param boolean True to check for duplicates first 
     189     * @return array List of created record IDs 
     190     */ 
     191    function insertMultiple($recset, $check=false) 
     192    { 
     193        $ids = array(); 
     194        if (is_object($recset) && is_a($recset, rcube_result_set)) { 
     195            while ($row = $recset->next()) { 
     196                if ($insert = $this->insert($row, $check)) 
     197                    $ids[] = $insert; 
     198            } 
     199        } 
     200        return $ids; 
     201    } 
     202 
     203    /** 
    141204     * Update a specific contact record 
    142205     * 
    143206     * @param mixed Record identifier 
    144207     * @param array Assoziative array with save data 
     208     *  Keys:   Field name with optional section in the form FIELD:SECTION 
     209     *  Values: Field value. Can be either a string or an array of strings for multiple values 
    145210     * @return boolean True on success, False on error 
    146211     */ 
     
    177242     * List all active contact groups of this source 
    178243     * 
     244     * @param string  Optional search string to match group name 
    179245     * @return array  Indexed list of contact groups, each a hash array 
    180246     */ 
    181     function list_groups() 
     247    function list_groups($search = null) 
    182248    { 
    183249        /* empty for address books don't supporting groups */ 
     
    261327        return array(); 
    262328    } 
     329 
     330 
     331    /** 
     332     * Utility function to return all values of a certain data column 
     333     * either as flat list or grouped by subtype 
     334     * 
     335     * @param string Col name 
     336     * @param array  Record data array as used for saving 
     337     * @param boolean True to return one array with all values, False for hash array with values grouped by type 
     338     * @return array List of column values 
     339     */ 
     340    function get_col_values($col, $data, $flat = false) 
     341    { 
     342        $out = array(); 
     343        foreach ($data as $c => $values) { 
     344            if (strpos($c, $col) === 0) { 
     345                if ($flat) { 
     346                    $out = array_merge($out, (array)$values); 
     347                } 
     348                else { 
     349                    list($f, $type) = explode(':', $c); 
     350                    $out[$type] = array_merge((array)$out[$type], (array)$values); 
     351                } 
     352            } 
     353        } 
     354       
     355        return $out; 
     356    } 
     357     
    263358} 
    264359 
  • trunk/roundcubemail/program/include/rcube_browser.php

    r4410 r4424  
    6969        $this->pngalpha = $this->mz || $this->safari || ($this->ie && $this->ver>=5.5) || 
    7070            ($this->ie && $this->ver>=5 && $this->mac) || ($this->opera && $this->ver>=7) ? true : false; 
     71        $this->imgdata = !$this->ie; 
    7172    } 
    7273} 
  • trunk/roundcubemail/program/include/rcube_contacts.php

    r4410 r4424  
    4848 
    4949    // public properties 
    50     var $primary_key = 'contact_id'; 
    51     var $readonly = false; 
    52     var $groups = true; 
    53     var $list_page = 1; 
    54     var $page_size = 10; 
    55     var $group_id = 0; 
    56     var $ready = false; 
     50    public $primary_key = 'contact_id'; 
     51    public $readonly = false; 
     52    public $groups = true; 
     53    public $list_page = 1; 
     54    public $page_size = 10; 
     55    public $group_id = 0; 
     56    public $ready = false; 
     57    public $coltypes = array('name', 'firstname', 'surname', 'middlename', 'prefix', 'suffix', 'nickname', 
     58      'jobtitle', 'organization', 'department', 'assistant', 'manager', 
     59      'gender', 'maidenname', 'spouse', 'email', 'phone', 'address', 
     60      'birthday', 'anniversary', 'website', 'im', 'notes', 'photo'); 
    5761 
    5862 
     
    153157     * List the current set of contact records 
    154158     * 
    155      * @param  array   List of cols to show 
     159     * @param  array   List of cols to show, Null means all 
    156160     * @param  int     Only return this number of records, use negative values for tail 
    157161     * @param  boolean True to skip the count query (select only) 
     
    188192            $this->group_id); 
    189193 
     194        // determine whether we have to parse the vcard or if only db cols are requested 
     195        $read_vcard = !$cols || count(array_intersect($cols, $this->table_cols)) < count($cols); 
     196         
    190197        while ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) { 
    191198            $sql_arr['ID'] = $sql_arr[$this->primary_key]; 
     199 
     200            if ($read_vcard) 
     201                $sql_arr = $this->convert_db_data($sql_arr); 
     202            else 
     203                $sql_arr['email'] = preg_split('/,\s*/', $sql_arr['email']); 
     204             
    192205            // make sure we have a name to display 
    193206            if (empty($sql_arr['name'])) 
    194                 $sql_arr['name'] = $sql_arr['email']; 
     207                $sql_arr['name'] = $sql_arr['email'][0]; 
     208 
    195209            $this->result->add($sql_arr); 
    196210        } 
     
    223237     * @param boolean True to skip the count query (select only) 
    224238     * @param array   List of fields that cannot be empty 
    225      * @return Indexed list of contact records and 'count' value 
     239     * @return object rcube_result_set Contact records and 'count' value 
    226240     */ 
    227241    function search($fields, $value, $strict=false, $select=true, $nocount=false, $required=array()) 
     
    346360 
    347361        if ($sql_arr = $this->db->fetch_assoc()) { 
    348             $sql_arr['ID'] = $sql_arr[$this->primary_key]; 
     362            $record = $this->convert_db_data($sql_arr); 
    349363            $this->result = new rcube_result_set(1); 
    350             $this->result->add($sql_arr); 
    351         } 
    352  
    353         return $assoc && $sql_arr ? $sql_arr : $this->result; 
     364            $this->result->add($record); 
     365        } 
     366 
     367        return $assoc && $record ? $record : $this->result; 
    354368    } 
    355369 
     
    390404    function insert($save_data, $check=false) 
    391405    { 
    392         if (is_object($save_data) && is_a($save_data, rcube_result_set)) 
    393             return $this->insert_recset($save_data, $check); 
     406        if (!is_array($save_data)) 
     407            return false; 
    394408 
    395409        $insert_id = $existing = false; 
    396410 
    397         if ($check) 
    398             $existing = $this->search('email', $save_data['email'], true, false); 
    399  
     411        if ($check) { 
     412            foreach ($save_data as $col => $values) { 
     413                if (strpos($col, 'email') === 0) { 
     414                    foreach ((array)$values as $email) { 
     415                        if ($existing = $this->search('email', $email, true, false)) 
     416                            break 2; 
     417                    } 
     418                } 
     419            } 
     420        } 
     421 
     422        $save_data = $this->convert_save_data($save_data); 
    400423        $a_insert_cols = $a_insert_values = array(); 
    401424 
    402         foreach ($this->table_cols as $col) 
    403             if (isset($save_data[$col])) { 
    404                 $a_insert_cols[]   = $this->db->quoteIdentifier($col); 
    405                 $a_insert_values[] = $this->db->quote($save_data[$col]); 
    406             } 
     425        foreach ($save_data as $col => $value) { 
     426            $a_insert_cols[]   = $this->db->quoteIdentifier($col); 
     427            $a_insert_values[] = $this->db->quote($value); 
     428        } 
    407429 
    408430        if (!$existing->count && !empty($a_insert_cols)) { 
     
    427449 
    428450    /** 
    429      * Insert new contacts for each row in set 
    430      */ 
    431     function insert_recset($result, $check=false) 
    432     { 
    433         $ids = array(); 
    434         while ($row = $result->next()) { 
    435             if ($insert = $this->insert($row, $check)) 
    436                 $ids[] = $insert; 
    437         } 
    438         return $ids; 
    439     } 
    440  
    441  
    442     /** 
    443451     * Update a specific contact record 
    444452     * 
     
    451459        $updated = false; 
    452460        $write_sql = array(); 
    453  
    454         foreach ($this->table_cols as $col) 
    455             if (isset($save_cols[$col])) 
    456                 $write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col), 
    457                     $this->db->quote($save_cols[$col])); 
     461        $record = $this->get_record($id, true); 
     462        $save_cols = $this->convert_save_data($save_cols, $record); 
     463 
     464        foreach ($save_cols as $col => $value) { 
     465            $write_sql[] = sprintf("%s=%s", $this->db->quoteIdentifier($col), $this->db->quote($value)); 
     466        } 
    458467 
    459468        if (!empty($write_sql)) { 
     
    469478 
    470479            $updated = $this->db->affected_rows(); 
     480            $this->result = null;  // clear current result (from get_record()) 
    471481        } 
    472482 
    473483        return $updated; 
     484    } 
     485     
     486     
     487    private function convert_db_data($sql_arr) 
     488    { 
     489        $record = array(); 
     490        $record['ID'] = $sql_arr[$this->primary_key]; 
     491         
     492        if ($sql_arr['vcard']) { 
     493            unset($sql_arr['email']); 
     494            $vcard = new rcube_vcard($sql_arr['vcard']); 
     495            $record += $vcard->get_assoc() + $sql_arr; 
     496        } 
     497        else { 
     498            $record += $sql_arr; 
     499            $record['email'] = preg_split('/,\s*/', $record['email']); 
     500        } 
     501         
     502        return $record; 
     503    } 
     504 
     505 
     506    private function convert_save_data($save_data, $record = array()) 
     507    { 
     508        $out = array(); 
     509 
     510        // copy values into vcard object 
     511        $vcard = new rcube_vcard($record['vcard'] ? $record['vcard'] : $save_data['vcard']); 
     512        $vcard->reset(); 
     513        foreach ($save_data as $key => $values) { 
     514            list($field, $section) = explode(':', $key); 
     515            foreach ((array)$values as $value) { 
     516                if (isset($value)) 
     517                    $vcard->set($field, $value, $section); 
     518            } 
     519        } 
     520        $out['vcard'] = $vcard->export(); 
     521 
     522        foreach ($this->table_cols as $col) { 
     523            $key = $col; 
     524            if (!isset($save_data[$key])) 
     525                $key .= ':home'; 
     526            if (isset($save_data[$key])) 
     527                $out[$col] = is_array($save_data[$key]) ? join(',', $save_data[$key]) : $save_data[$key]; 
     528        } 
     529 
     530        // save all e-mails in database column 
     531        $out['email'] = join(", ", $vcard->email); 
     532 
     533        return $out; 
    474534    } 
    475535 
  • trunk/roundcubemail/program/include/rcube_ldap.php

    r4410 r4424  
    2727class rcube_ldap extends rcube_addressbook 
    2828{ 
    29   var $conn; 
    30   var $prop = array(); 
    31   var $fieldmap = array(); 
    32  
    33   var $filter = ''; 
    34   var $result = null; 
    35   var $ldap_result = null; 
    36   var $sort_col = ''; 
    37   var $mail_domain = ''; 
    38   var $debug = false; 
     29  protected $conn; 
     30  protected $prop = array(); 
     31  protected $fieldmap = array(); 
     32 
     33  protected $filter = ''; 
     34  protected $result = null; 
     35  protected $ldap_result = null; 
     36  protected $sort_col = ''; 
     37  protected $mail_domain = ''; 
     38  protected $debug = false; 
    3939 
    4040  /** public properties */ 
    41   var $primary_key = 'ID'; 
    42   var $readonly = true; 
    43   var $list_page = 1; 
    44   var $page_size = 10; 
    45   var $ready = false; 
     41  public $primary_key = 'ID'; 
     42  public $readonly = true; 
     43  public $list_page = 1; 
     44  public $page_size = 10; 
     45  public $ready = false; 
     46  public $coltypes = array(); 
    4647 
    4748 
     
    5859    $this->prop = $p; 
    5960 
    60     foreach ($p as $prop => $value) 
    61       if (preg_match('/^(.+)_field$/', $prop, $matches)) 
    62         $this->fieldmap[$matches[1]] = $this->_attr_name(strtolower($value)); 
     61    // fieldmap property is given 
     62    if (is_array($p['fieldmap'])) { 
     63      foreach ($p['fieldmap'] as $rf => $lf) 
     64        $this->fieldmap[$rf] = $this->_attr_name(strtolower($lf)); 
     65    } 
     66    else { 
     67      // read deprecated *_field properties to remain backwards compatible 
     68      foreach ($p as $prop => $value) 
     69        if (preg_match('/^(.+)_field$/', $prop, $matches)) 
     70          $this->fieldmap[$matches[1]] = $this->_attr_name(strtolower($value)); 
     71    } 
     72     
     73    // use fieldmap to advertise supported coltypes to the application 
     74    foreach ($this->fieldmap as $col => $lf) { 
     75      list($col, $type) = explode(':', $col); 
     76      if (!is_array($this->coltypes[$col])) { 
     77        $subtypes = $type ? array($type) : null; 
     78        $this->coltypes[$col] = array('limit' => 2, 'subtypes' => $subtypes); 
     79      } 
     80      else if ($type) { 
     81        $this->coltypes[$col]['subtypes'][] = $type; 
     82        $this->coltypes[$col]['limit']++; 
     83      } 
     84      if ($type && !$this->fieldmap[$col]) 
     85        $this->fieldmap[$col] = $lf; 
     86    } 
     87     
     88    if ($this->fieldmap['street'] && $this->fieldmap['locality']) 
     89      $this->coltypes['address'] = array('limit' => 1); 
     90    else if ($this->coltypes['address']) 
     91      $this->coltypes['address'] = array('type' => 'textarea', 'childs' => null, 'limit' => 1, 'size' => 40); 
    6392 
    6493    // make sure 'required_fields' is an array 
     
    456485      if ($entry && ($rec = ldap_get_attributes($this->conn, $entry))) 
    457486      { 
    458         $this->_debug("S: OK"); 
     487        $this->_debug("S: OK"/* . print_r($rec, true)*/); 
    459488 
    460489        $rec = array_change_key_case($rec, CASE_LOWER); 
     
    483512    $newentry = array(); 
    484513    $newentry['objectClass'] = $this->prop['LDAP_Object_Classes']; 
    485     foreach ($save_cols as $col => $val) { 
    486       $fld = $this->_map_field($col); 
     514    foreach ($this->fieldmap as $col => $fld) { 
     515      $val = $save_cols[$col]; 
     516      if (is_array($val)) 
     517        $val = array_filter($val);  // remove empty entries 
    487518      if ($fld && $val) { 
    488519        // The field does exist, add it to the entry. 
     
    492523 
    493524    // Verify that the required fields are set. 
    494     // We know that the email address is required as a default of rcube, so 
    495     // we will default its value into any unfilled required fields. 
    496525    foreach ($this->prop['required_fields'] as $fld) { 
     526      $missing = null; 
    497527      if (!isset($newentry[$fld])) { 
    498         $newentry[$fld] = $newentry[$this->_map_field('email')]; 
    499       } // end if 
    500     } // end foreach 
     528        $missing[] = $fld; 
     529      } 
     530    } 
     531     
     532    // abort process if requiered fields are missing 
     533    // TODO: generate message saying which fields are missing 
     534    if ($missing) { 
     535      $this->set_error(self::ERROR_INCOMPLETE, 'formincomplete'); 
     536      return false; 
     537    } 
    501538 
    502539    // Build the new entries DN. 
    503     $dn = $this->prop['LDAP_rdn'].'='.rcube_ldap::quote_string($newentry[$this->prop['LDAP_rdn']], true) 
    504       .','.$this->prop['base_dn']; 
     540    $dn = $this->prop['LDAP_rdn'].'='.rcube_ldap::quote_string($newentry[$this->prop['LDAP_rdn']], true).','.$this->prop['base_dn']; 
    505541 
    506542    $this->_debug("C: Add [dn: $dn]: ".print_r($newentry, true)); 
     
    509545    if ($res === FALSE) { 
    510546      $this->_debug("S: ".ldap_error($this->conn)); 
     547      $this->set_error(self::ERROR_SAVING, 'errorsaving'); 
    511548      return false; 
    512549    } // end if 
     
    534571    $replacedata = array(); 
    535572    $deletedata = array(); 
    536     foreach ($save_cols as $col => $val) { 
    537       $fld = $this->_map_field($col); 
     573    foreach ($this->fieldmap as $col => $fld) { 
     574      $val = $save_cols[$col]; 
    538575      if ($fld) { 
    539576        // The field does exist compare it to the ldap record. 
     
    567604      if (!ldap_mod_del($this->conn, $dn, $deletedata)) { 
    568605        $this->_debug("S: ".ldap_error($this->conn)); 
     606        $this->set_error(self::ERROR_SAVING, 'errorsaving'); 
    569607        return false; 
    570608      } 
     
    576614      if ($replacedata[$this->prop['LDAP_rdn']]) { 
    577615        $newdn = $this->prop['LDAP_rdn'].'=' 
    578           .rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true) 
    579           .','.$this->prop['base_dn'];  
     616          .rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true) 
     617          .','.$this->prop['base_dn']; 
    580618        if ($dn != $newdn) { 
    581619          $newrdn = $this->prop['LDAP_rdn'].'=' 
    582             .rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true); 
     620            .rcube_ldap::quote_string($replacedata[$this->prop['LDAP_rdn']], true); 
    583621          unset($replacedata[$this->prop['LDAP_rdn']]); 
    584622        } 
     
    590628          $this->_debug("S: ".ldap_error($this->conn)); 
    591629          return false; 
    592         } 
     630        } 
    593631        $this->_debug("S: OK"); 
    594632      } // end if 
     
    600638      if (!ldap_mod_add($this->conn, $dn, $newdata)) { 
    601639        $this->_debug("S: ".ldap_error($this->conn)); 
     640        $this->set_error(self::ERROR_SAVING, 'errorsaving'); 
    602641        return false; 
    603642      } 
     
    639678      if ($res === FALSE) { 
    640679        $this->_debug("S: ".ldap_error($this->conn)); 
     680        $this->set_error(self::ERROR_SAVING, 'errorsaving'); 
    641681        return false; 
    642682      } // end if 
     
    680720  private function _ldap2result($rec) 
    681721  { 
    682     global $RCMAIL; 
    683  
    684722    $out = array(); 
    685723     
     
    689727    foreach ($this->fieldmap as $rf => $lf) 
    690728    { 
    691       if ($rec[$lf]['count']) { 
    692         if ($rf == 'email' && $this->mail_domain && !strpos($rec[$lf][0], '@')) 
    693           $out[$rf] = sprintf('%s@%s', $rec[$lf][0], $this->mail_domain); 
     729      for ($i=0; $i < $rec[$lf]['count']; $i++) { 
     730        if (!($value = $rec[$lf][$i])) 
     731          continue; 
     732        if ($rf == 'email' && $this->mail_domain && !strpos($value, '@')) 
     733          $out[$rf][] = sprintf('%s@%s', $value, $this->mail_domain); 
     734        else if (in_array($rf, array('street','zipcode','locality','country','region'))) 
     735          $out['address'][$i][$rf] = $value; 
     736        else if ($rec[$lf]['count'] > 1) 
     737          $out[$rf][] = $value; 
    694738        else 
    695           $out[$rf] = $rec[$lf][0]; 
     739          $out[$rf] = $value; 
    696740      } 
    697741    } 
     
    742786  function quote_string($str, $dn=false) 
    743787  { 
     788    // take firt entry if array given 
     789    if (is_array($str)) 
     790      $str = reset($str); 
     791     
    744792    if ($dn) 
    745793      $replace = array(','=>'\2c', '='=>'\3d', '+'=>'\2b', '<'=>'\3c', 
  • trunk/roundcubemail/program/include/rcube_plugin.php

    r4410 r4424  
    8484   */ 
    8585  abstract function init(); 
    86    
     86 
     87 
     88  /** 
     89   * Attempt to load the given plugin which is required for the current plugin 
     90   * 
     91   * @param string Plugin name 
     92   * @return boolean True on success, false on failure 
     93   */ 
     94  public function require_plugin($plugin_name) 
     95  { 
     96    return $this->api->load_plugin($plugin_name); 
     97  } 
     98 
     99 
    87100  /** 
    88101   * Load local config file from plugins directory. 
  • trunk/roundcubemail/program/include/rcube_plugin_api.php

    r4414 r4424  
    110110    $this->config = $rcmail->config; 
    111111 
    112     $plugins_dir = dir($this->dir); 
    113     $plugins_dir = unslashify($plugins_dir->path); 
    114112    $plugins_enabled = (array)$rcmail->config->get('plugins', array()); 
    115  
    116113    foreach ($plugins_enabled as $plugin_name) { 
    117       $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; 
    118  
    119       if (file_exists($fn)) { 
    120         include($fn); 
    121  
    122         // instantiate class if exists 
    123         if (class_exists($plugin_name, false)) { 
    124           $plugin = new $plugin_name($this); 
    125           // check inheritance... 
    126           if (is_subclass_of($plugin, 'rcube_plugin')) { 
    127             // ... task, request type and framed mode 
    128             if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $rcmail->task)) 
    129                 && (!$plugin->noajax || is_a($this->output, 'rcube_template')) 
    130                 && (!$plugin->noframe || empty($_REQUEST['_framed'])) 
    131             ) { 
    132               $plugin->init(); 
    133               $this->plugins[] = $plugin; 
    134             } 
    135           } 
    136         } 
    137         else { 
    138           raise_error(array('code' => 520, 'type' => 'php', 
    139             'file' => __FILE__, 'line' => __LINE__, 
    140             'message' => "No plugin class $plugin_name found in $fn"), true, false); 
    141         } 
    142       } 
    143       else { 
    144         raise_error(array('code' => 520, 'type' => 'php', 
    145           'file' => __FILE__, 'line' => __LINE__, 
    146           'message' => "Failed to load plugin file $fn"), true, false); 
    147       } 
     114      $this->load_plugin($plugin_name); 
    148115    } 
    149116     
     
    159126       
    160127      // load required core plugin if no derivate was found 
    161       if (!$loaded) { 
    162         $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; 
    163  
    164         if (file_exists($fn)) { 
    165           include_once($fn); 
    166            
    167           if (class_exists($plugin_name, false)) { 
    168             $plugin = new $plugin_name($this); 
    169             // check inheritance 
    170             if (is_subclass_of($plugin, 'rcube_plugin')) { 
    171               if (!$plugin->task || preg_match('/('.$plugin->task.')/i', $rcmail->task)) { 
    172                 $plugin->init(); 
    173                 $this->plugins[] = $plugin; 
    174               } 
    175               $loaded = true; 
    176             } 
    177           } 
    178         } 
    179       } 
    180        
     128      if (!$loaded) 
     129        $loaded = $this->load_plugin($plugin_name); 
     130 
    181131      // trigger fatal error if still not loaded 
    182132      if (!$loaded) { 
    183133        raise_error(array('code' => 520, 'type' => 'php', 
    184           'file' => __FILE__, 'line' => __LINE__, 
    185           'message' => "Requried plugin $plugin_name was not loaded"), true, true); 
     134          'file' => __FILE__, 'line' => __LINE__, 
     135          'message' => "Requried plugin $plugin_name was not loaded"), true, true); 
    186136      } 
    187137    } 
     
    191141     
    192142    // maybe also register a shudown function which triggers shutdown functions of all plugin objects 
     143  } 
     144 
     145 
     146  /** 
     147   * Load the specified plugin 
     148   * 
     149   * @param string Plugin name 
     150   * @return boolean True on success, false if not loaded or failure 
     151   */ 
     152  public function load_plugin($plugin_name) 
     153  { 
     154    static $plugins_dir; 
     155     
     156    $rcmail = rcmail::get_instance(); 
     157     
     158    if (!$plugins_dir) { 
     159      $dir = dir($this->dir); 
     160      $plugins_dir = unslashify($dir->path); 
     161    } 
     162     
     163    // plugin already loaded 
     164    if ($this->plugins[$plugin_name] || class_exists($plugin_name, false)) 
     165      return true; 
     166     
     167    $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; 
     168 
     169    if (file_exists($fn)) { 
     170      include($fn); 
     171 
     172      // instantiate class if exists 
     173      if (class_exists($plugin_name, false)) { 
     174        $plugin = new $plugin_name($this); 
     175        // check inheritance... 
     176        if (is_subclass_of($plugin, 'rcube_plugin')) { 
     177          // ... task, request type and framed mode 
     178          if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $rcmail->task)) /* 
     179              && (!$plugin->noajax || is_a($rcmail->output, 'rcube_template')) 
     180              && (!$plugin->noframe || empty($_REQUEST['_framed']))*/ 
     181          ) { 
     182            $plugin->init(); 
     183            $this->plugins[$plugin_name] = $plugin; 
     184          } 
     185          return true; 
     186        } 
     187      } 
     188      else { 
     189        raise_error(array('code' => 520, 'type' => 'php', 
     190          'file' => __FILE__, 'line' => __LINE__, 
     191          'message' => "No plugin class $plugin_name found in $fn"), true, false); 
     192      } 
     193    } 
     194    else { 
     195      raise_error(array('code' => 520, 'type' => 'php', 
     196        'file' => __FILE__, 'line' => __LINE__, 
     197        'message' => "Failed to load plugin file $fn"), true, false); 
     198    } 
     199     
     200    return false; 
    193201  } 
    194202   
  • trunk/roundcubemail/program/include/rcube_shared.inc

    r4410 r4424  
    486486} 
    487487 
     488 
     489/** 
     490 * Detect image type of the given binary data by checking magic numbers 
     491 * 
     492 * @param string  Binary file content 
     493 * @return string Detected mime-type or jpeg as fallback 
     494 */ 
     495function rc_image_content_type($data) 
     496{ 
     497    $type = 'jpeg'; 
     498    if      (preg_match('/^\x89\x50\x4E\x47/', $data)) $type = 'png'; 
     499    else if (preg_match('/^\x47\x49\x46\x38/', $data)) $type = 'gif'; 
     500    else if (preg_match('/^\x00\x00\x01\x00/', $data)) $type = 'ico'; 
     501//  else if (preg_match('/^\xFF\xD8\xFF\xE0/', $data)) $type = 'jpeg'; 
     502 
     503    return 'image/' . $type; 
     504} 
     505 
     506 
    488507/** 
    489508 * A method to guess encoding of a string. 
  • trunk/roundcubemail/program/include/rcube_template.php

    r4415 r4424  
    997997 
    998998        // we already have a <form> tag 
    999         if ($attrib['form']) 
     999        if ($attrib['form']) { 
     1000            if ($this->framed || !empty($_REQUEST['_framed'])) 
     1001                $hidden->add(array('name' => '_framed', 'value' => '1')); 
    10001002            return $hidden->show() . $content; 
     1003        } 
    10011004        else 
    10021005            return $this->form_tag($attrib, $hidden->show() . $content); 
  • trunk/roundcubemail/program/include/rcube_vcard.php

    r4410 r4424  
    66 |                                                                       | 
    77 | This file is part of the Roundcube Webmail client                     | 
    8  | Copyright (C) 2008-2009, The Roundcube Dev Team                       | 
     8 | Copyright (C) 2008-2011, The Roundcube Dev Team                       | 
    99 | Licensed under the GNU GPL                                            | 
    1010 |                                                                       | 
     
    3434    'N' => array(array('','','','','')), 
    3535  ); 
     36  private $fieldmap = array( 
     37    'phone'    => 'TEL', 
     38    'birthday' => 'BDAY', 
     39    'website'  => 'URL', 
     40    'notes'    => 'NOTE', 
     41    'email'    => 'EMAIL', 
     42    'address'  => 'ADR', 
     43    'gender'      => 'X-GENDER', 
     44    'maidenname'  => 'X-MAIDENNAME', 
     45    'anniversary' => 'X-ANNIVERSARY', 
     46    'assistant'   => 'X-ASSISTANT', 
     47    'manager'     => 'X-MANAGER', 
     48    'spouse'      => 'X-SPOUSE', 
     49  ); 
     50  private $typemap = array('iPhone' => 'mobile', 'CELL' => 'mobile'); 
     51  private $phonetypemap = array('HOME1' => 'HOME', 'BUSINESS1' => 'WORK', 'BUSINESS2' => 'WORK2', 'WORKFAX' => 'BUSINESSFAX'); 
     52  private $addresstypemap = array('BUSINESS' => 'WORK'); 
     53  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'); 
    3654 
    3755  public $business = false; 
     
    108126 
    109127  /** 
     128   * Return vCard data as associative array to be unsed in Roundcube address books 
     129   * 
     130   * @return array Hash array with key-value pairs 
     131   */ 
     132  public function get_assoc() 
     133  { 
     134    $out = array('name' => $this->displayname); 
     135    $typemap = $this->typemap; 
     136     
     137    // copy name fields to output array 
     138    foreach (array('firstname','surname','middlename','nickname','organization') as $col) 
     139      $out[$col] = $this->$col; 
     140     
     141    $out['prefix'] = $this->raw['N'][0][3]; 
     142    $out['suffix'] = $this->raw['N'][0][4]; 
     143     
     144    // convert from raw vcard data into associative data for Roundcube 
     145    foreach (array_flip($this->fieldmap) as $tag => $col) { 
     146      foreach ((array)$this->raw[$tag] as $i => $raw) { 
     147        if (is_array($raw)) { 
     148          $k = -1; 
     149          $key = $col; 
     150          $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]); 
     151          while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref')) 
     152            $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]); 
     153          if ($subtype) 
     154            $key .= ':' . $subtype; 
     155 
     156          // split ADR values into assoc array 
     157          if ($tag == 'ADR') { 
     158            list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw; 
     159            $out[$key][] = $value; 
     160          } 
     161          else 
     162            $out[$key][] = $raw[0]; 
     163        } 
     164        else { 
     165          $out[$col][] = $raw; 
     166        } 
     167      } 
     168    } 
     169     
     170    // handle special IM fields as used by Apple 
     171    foreach ($this->immap as $tag => $type) { 
     172      foreach ((array)$this->raw[$tag] as $i => $raw) { 
     173        $out['im:'.$type][] = $raw[0]; 
     174      } 
     175    } 
     176     
     177    // copy photo data 
     178    if ($this->raw['PHOTO']) 
     179      $out['photo'] = $this->raw['PHOTO'][0][0]; 
     180     
     181    return $out; 
     182  } 
     183 
     184 
     185  /** 
    110186   * Convert the data structure into a vcard 3.0 string 
    111187   */ 
     
    113189  { 
    114190    return self::rfc2425_fold(self::vcard_encode($this->raw)); 
     191  } 
     192   
     193   
     194  /** 
     195   * Clear the given fields in the loaded vcard data 
     196   * 
     197   * @param array List of field names to be reset 
     198   */ 
     199  public function reset($fields = null) 
     200  { 
     201    if (!$fields) 
     202      $fields = array_merge(array_values($this->fieldmap), array_keys($this->immap), array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY')); 
     203     
     204    foreach ($fields as $f) 
     205      unset($this->raw[$f]); 
     206 
     207    if (!$this->raw['N']) 
     208      $this->raw['N'] = array(array('','','','','')); 
     209    if (!$this->raw['FN']) 
     210      $this->raw['FN'] = array(); 
     211       
     212    $this->email = array(); 
    115213  } 
    116214 
     
    121219   * @param string Field name 
    122220   * @param string Field value 
    123    * @param string Section name 
    124    */ 
    125   public function set($field, $value, $section = 'HOME') 
    126   { 
     221   * @param string Type/section name 
     222   */ 
     223  public function set($field, $value, $type = 'HOME') 
     224  { 
     225    $field = strtolower($field); 
     226    $type = strtoupper($type); 
     227    $typemap = array_flip($this->typemap); 
     228     
    127229    switch ($field) { 
    128230      case 'name': 
     
    131233        break; 
    132234         
     235      case 'surname': 
     236        $this->raw['N'][0][0] = $value; 
     237        break; 
     238         
    133239      case 'firstname': 
    134240        $this->raw['N'][0][1] = $value; 
    135241        break; 
    136242         
    137       case 'surname': 
    138         $this->raw['N'][0][0] = $value; 
    139         break; 
    140        
     243      case 'middlename': 
     244        $this->raw['N'][0][2] = $value; 
     245        break; 
     246         
     247      case 'prefix': 
     248        $this->raw['N'][0][3] = $value; 
     249        break; 
     250         
     251      case 'suffix': 
     252        $this->raw['N'][0][4] = $value; 
     253        break; 
     254         
    141255      case 'nickname': 
    142256        $this->raw['NICKNAME'][0][0] = $value; 
     
    147261        break; 
    148262         
     263      case 'photo': 
     264        $encoded = !preg_match('![^a-z0-9/=+-]!i', $value); 
     265        $this->raw['PHOTO'][0] = array(0 => $encoded ? $value : base64_encode($value), 'BASE64' => true); 
     266        break; 
     267         
    149268      case 'email': 
    150         $index = $this->get_type_index('EMAIL', $section); 
    151         if (!is_array($this->raw['EMAIL'][$index])) { 
    152           $this->raw['EMAIL'][$index] = array(0 => $value, 'type' => array('INTERNET', $section, 'pref')); 
    153         } 
    154         else { 
    155           $this->raw['EMAIL'][$index][0] = $value; 
     269        $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type))); 
     270        $this->email[] = $value; 
     271        break; 
     272         
     273      case 'im': 
     274        // save IM subtypes into extension fields 
     275        $typemap = array_flip($this->immap); 
     276        if ($field = $typemap[strtolower($type)]) 
     277          $this->raw[$field][] = array(0 => $value); 
     278        break; 
     279 
     280      case 'birthday': 
     281        if ($val = @strtotime($value)) 
     282          $this->raw['BDAY'][] = array(0 => date('Y-m-d', $val), 'value' => array('date')); 
     283        break; 
     284 
     285      case 'address': 
     286        if ($this->addresstypemap[$type]) 
     287          $type = $this->addresstypemap[$type]; 
     288 
     289        $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']); 
     290 
     291        // fall through if not empty 
     292        if (!strlen(join('', $value))) 
     293          break; 
     294 
     295      default: 
     296        if ($field == 'phone' && $this->phonetypemap[$type]) 
     297          $type = $this->phonetypemap[$type]; 
     298 
     299        if (($tag = $this->fieldmap[$field]) && (is_array($value) || strlen($value))) { 
     300          $index = count($this->raw[$tag]); 
     301          $this->raw[$tag][$index] = (array)$value; 
     302          if ($type) 
     303            $this->raw[$tag][$index]['type'] = array(($typemap[$type] ? $typemap[$type] : $type)); 
    156304        } 
    157305        break; 
  • trunk/roundcubemail/program/js/app.js

    r4422 r4424  
    320320        if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform) { 
    321321          this.enable_command('save', true); 
    322           $("input[type='text']").first().select(); 
     322          this.enable_command('upload-photo', this.env.coltypes.photo ? true : false); 
     323          this.enable_command('delete-photo', this.env.coltypes.photo && this.env.action == 'edit'); 
     324 
     325          for (var col in this.env.coltypes) 
     326            this.init_edit_field(col, null); 
     327 
     328          $('.contactfieldgroup .row a.deletebutton').click(function(){ ref.delete_edit_field(this); return false }); 
     329 
     330          $('select.addfieldmenu').change(function(e){ 
     331            ref.insert_edit_field($(this).val(), $(this).attr('rel'), this); 
     332            this.selectedIndex = 0; 
     333          }); 
     334 
     335          $("input[type='text']").first().focus(); 
    323336        } 
    324337        else if (this.gui_objects.qsearchbox) { 
     
    640653              break; 
    641654            } 
     655             
     656            // clear empty input fields 
     657            $('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; }); 
    642658          } 
    643659 
     
    9971013      case 'export': 
    9981014        if (this.contact_list.rowcount > 0) { 
    999           var add_url = (this.env.source ? '_source='+urlencode(this.env.source)+'&' : ''); 
    1000           if (this.env.search_request) 
    1001             add_url += '_search='+this.env.search_request; 
    1002  
    1003           this.goto_url('export', add_url); 
    1004         } 
     1015          this.goto_url('export', { _source:this.env.source, _gid:this.env.group, _search:this.env.search_request }); 
     1016        } 
     1017        break; 
     1018         
     1019      case 'upload-photo': 
     1020        this.upload_contact_photo(props); 
     1021        break; 
     1022 
     1023      case 'delete-photo': 
     1024        this.replace_contact_photo('-del-'); 
    10051025        break; 
    10061026 
     
    11591179  this.is_framed = function() 
    11601180  { 
    1161     return (this.env.framed && parent.rcmail); 
     1181    return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command); 
    11621182  }; 
    11631183 
     
    31783198    // create hidden iframe and post upload form 
    31793199    if (send) { 
    3180       var ts = new Date().getTime(); 
    3181       var frame_name = 'rcmupload'+ts; 
    3182  
    3183       // have to do it this way for IE 
    3184       // otherwise the form will be posted to a new window 
    3185       if (document.all) { 
    3186         var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>'; 
    3187         document.body.insertAdjacentHTML('BeforeEnd',html); 
    3188       } 
    3189       else { // for standards-compilant browsers 
    3190         var frame = document.createElement('iframe'); 
    3191         frame.name = frame_name; 
    3192         frame.style.border = 'none'; 
    3193         frame.style.width = 0; 
    3194         frame.style.height = 0; 
    3195         frame.style.visibility = 'hidden'; 
    3196         document.body.appendChild(frame); 
    3197       } 
    3198  
    3199       // handle upload errors, parsing iframe content in onload 
    3200       $(frame_name).bind('load', {ts:ts}, function(e) { 
     3200      this.async_upload_form(form, 'upload', function(e) { 
    32013201        var d, content = ''; 
    32023202        try { 
     
    32183218          rcmail.env.uploadframe = e.data.ts; 
    32193219      }); 
    3220  
    3221       form.target = frame_name; 
    3222       form.action = this.env.comm_path+'&_action=upload&_uploadid='+ts; 
    3223       form.setAttribute('enctype', 'multipart/form-data'); 
    3224       form.submit(); 
    32253220 
    32263221      // display upload indicator and cancel button 
     
    39803975 
    39813976 
     3977  this.init_edit_field = function(col, elem) 
     3978  { 
     3979    if (!elem) 
     3980      elem = $('.ff_' + col); 
     3981     
     3982    elem.focus(function(){ ref.focus_textfield(this); }) 
     3983      .blur(function(){ ref.blur_textfield(this); }) 
     3984      .each(function(){ this._placeholder = ref.env.coltypes[col].label; ref.blur_textfield(this); }); 
     3985  }; 
     3986 
     3987  this.insert_edit_field = function(col, section, menu) 
     3988  { 
     3989    // just make pre-defined input field visible 
     3990    var elem = $('#ff_'+col); 
     3991    if (elem.length) { 
     3992      elem.show().focus(); 
     3993      $(menu).children('option[value="'+col+'"]').attr('disabled', true); 
     3994    } 
     3995    else { 
     3996      var lastelem = $('.ff_'+col), 
     3997        appendcontainer = $('#contactsection'+section+' .contactcontroller'+col); 
     3998       
     3999      if (!appendcontainer.length) 
     4000        appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col).insertAfter($('#contactsection'+section+' .contactfieldgroup').last()); 
     4001 
     4002      if (appendcontainer.length && appendcontainer.get(0).nodeName == 'FIELDSET') { 
     4003        var input, colprop = this.env.coltypes[col], 
     4004          row = $('<div>').addClass('row'), 
     4005          cell = $('<div>').addClass('contactfieldcontent data'), 
     4006          label = $('<div>').addClass('contactfieldlabel label'); 
     4007           
     4008        if (colprop.subtypes_select) 
     4009          label.html(colprop.subtypes_select); 
     4010        else 
     4011          label.html(colprop.label); 
     4012 
     4013        var name_suffix = colprop.limit != 1 ? '[]' : ''; 
     4014        if (colprop.type == 'text' || colprop.type == 'date') { 
     4015          input = $('<input>') 
     4016            .addClass('ff_'+col) 
     4017            .attr('type', 'text') 
     4018            .attr('name', '_'+col+name_suffix) 
     4019            .attr('size', colprop.size) 
     4020            .appendTo(cell); 
     4021 
     4022          this.init_edit_field(col, input); 
     4023        } 
     4024        else if (colprop.type == 'composite') { 
     4025          var childcol, cp, first; 
     4026          for (var childcol in colprop.childs) { 
     4027            cp = colprop.childs[childcol]; 
     4028            input = $('<input>') 
     4029              .addClass('ff_'+childcol) 
     4030              .attr('type', 'text') 
     4031              .attr('name', '_'+childcol+name_suffix) 
     4032              .attr('size', cp.size) 
     4033              .appendTo(cell); 
     4034            cell.append(" "); 
     4035            this.init_edit_field(childcol, input); 
     4036            if (!first) first = input; 
     4037          } 
     4038          input = first;  // set focus to the first of this composite fields 
     4039        } 
     4040        else if (colprop.type == 'select') { 
     4041          input = $('<select>') 
     4042            .addClass('ff_'+col) 
     4043            .attr('name', '_'+col+name_suffix) 
     4044            .appendTo(cell); 
     4045           
     4046          var options = input.attr('options'); 
     4047          options[options.length] = new Option('---', ''); 
     4048          if (colprop.options) 
     4049            $.each(colprop.options, function(i, val){ options[options.length] = new Option(val, i); }); 
     4050        } 
     4051 
     4052        if (input) { 
     4053          var delbutton = $('<a href="#del"></a>') 
     4054            .addClass('contactfieldbutton deletebutton') 
     4055            .attr('title', this.get_label('delete')) 
     4056            .attr('rel', col) 
     4057            .html(this.env.delbutton) 
     4058            .click(function(){ ref.delete_edit_field(this); return false }) 
     4059            .appendTo(cell); 
     4060           
     4061          row.append(label).append(cell).appendTo(appendcontainer.show()); 
     4062          input.first().focus(); 
     4063           
     4064          // disable option if limit reached 
     4065          if (!colprop.count) colprop.count = 0; 
     4066          if (++colprop.count == colprop.limit && colprop.limit) 
     4067            $(menu).children('option[value="'+col+'"]').attr('disabled', true); 
     4068        } 
     4069      } 
     4070    } 
     4071  }; 
     4072 
     4073  this.delete_edit_field = function(elem) 
     4074  { 
     4075    var col = $(elem).attr('rel'), 
     4076      colprop = this.env.coltypes[col], 
     4077      fieldset = $(elem).parents('fieldset.contactfieldgroup'), 
     4078      addmenu = fieldset.parent().find('select.addfieldmenu'); 
     4079     
     4080    // just clear input but don't hide the last field 
     4081    if (--colprop.count <= 0 && colprop.visible) 
     4082      $(elem).parent().children('input').val('').blur(); 
     4083    else { 
     4084      $(elem).parents('div.row').remove(); 
     4085      // hide entire fieldset if no more rows 
     4086      if (!fieldset.children('div.row').length) 
     4087        fieldset.hide(); 
     4088    } 
     4089     
     4090    // enable option in add-field selector or insert it if necessary 
     4091    if (addmenu.length) { 
     4092      var option = addmenu.children('option[value="'+col+'"]'); 
     4093      if (option.length) 
     4094        option.attr('disabled', false); 
     4095      else 
     4096        option = $('<option>').attr('value', col).html(colprop.label).appendTo(addmenu); 
     4097      addmenu.show(); 
     4098    } 
     4099  }; 
     4100 
     4101 
     4102  this.upload_contact_photo = function(form) 
     4103  { 
     4104    if (form && form.elements._photo.value) { 
     4105      this.async_upload_form(form, 'upload-photo', function(e) { 
     4106        rcmail.set_busy(false, null, rcmail.photo_upload_id); 
     4107      }); 
     4108 
     4109      // display upload indicator 
     4110      this.photo_upload_id = this.set_busy(true, 'uploading'); 
     4111    } 
     4112  }; 
     4113   
     4114  this.replace_contact_photo = function(id) 
     4115  { 
     4116    $('#ff_photo').val(id); 
     4117     
     4118    var buttons = this.buttons['upload-photo']; 
     4119    for (var n=0; n < buttons.length; n++) 
     4120      $('#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto')); 
     4121     
     4122    var img_src = id == '-del-' ? this.env.photo_placeholder : 
     4123      this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + this.env.cid + '&_photo=' + id; 
     4124    $(this.gui_objects.contactphoto).children('img').attr('src', img_src); 
     4125     
     4126    this.enable_command('delete-photo', id != '-del-'); 
     4127  }; 
     4128   
     4129  this.photo_upload_end = function() 
     4130  { 
     4131    this.set_busy(false, null, this.photo_upload_id); 
     4132    delete this.photo_upload_id; 
     4133  }; 
     4134 
     4135 
    39824136  /*********************************************************/ 
    39834137  /*********        user settings methods          *********/ 
     
    45064660  }; 
    45074661 
     4662 
     4663  this.focus_textfield = function(elem) 
     4664  { 
     4665    elem._hasfocus = true; 
     4666    var $elem = $(elem); 
     4667    if ($elem.hasClass('placeholder') || $elem.val() == elem._placeholder) 
     4668      $elem.val('').removeClass('placeholder').attr('spellcheck', true); 
     4669  }; 
     4670 
     4671  this.blur_textfield = function(elem) 
     4672  { 
     4673    elem._hasfocus = false; 
     4674    var $elem = $(elem); 
     4675    if (elem._placeholder && (!$elem.val() || $elem.val() == elem._placeholder)) 
     4676      $elem.addClass('placeholder').attr('spellcheck', false).val(elem._placeholder); 
     4677  }; 
     4678   
    45084679  // write to the document/window title 
    45094680  this.set_pagetitle = function(title) 
     
    49525123  /*********        remote request methods        *********/ 
    49535124  /********************************************************/ 
     5125   
     5126  // compose a valid url with the given parameters 
     5127  this.url = function(action, query) 
     5128  { 
     5129    var querystring = typeof(query) == 'string' ? '&' + query : ''; 
     5130     
     5131    if (typeof action != 'string') 
     5132      query = action; 
     5133    else if (!query || typeof(query) != 'object') 
     5134      query = {}; 
     5135     
     5136    if (action) 
     5137      query._action = action; 
     5138    else 
     5139      query._action = this.env.action; 
     5140     
     5141    var base = this.env.comm_path; 
     5142 
     5143    // overwrite task name 
     5144    if (query._action.match(/([a-z]+)\/([a-z-_]+)/)) { 
     5145      query._action = RegExp.$2; 
     5146      base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); 
     5147    } 
     5148     
     5149    // remove undefined values 
     5150    var param = {}; 
     5151    for (var k in query) { 
     5152      if (typeof(query[k]) != 'undefined' && query[k] !== null) 
     5153        param[k] = query[k]; 
     5154    } 
     5155     
     5156    return base + '&' + $.param(param) + querystring; 
     5157  }; 
    49545158 
    49555159  this.redirect = function(url, lock) 
     
    49665170  this.goto_url = function(action, query, lock) 
    49675171  { 
    4968     var url = this.env.comm_path, 
    4969      querystring = query ? '&'+query : ''; 
    4970  
    4971     // overwrite task name 
    4972     if (action.match(/([a-z]+)\/([a-z-_]+)/)) { 
    4973       action = RegExp.$2; 
    4974       url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); 
    4975     } 
    4976  
    4977     this.redirect(url+'&_action='+action+querystring, lock); 
     5172    this.redirect(this.url(action, query)); 
    49785173  }; 
    49795174 
     
    49815176  this.http_request = function(action, query, lock) 
    49825177  { 
    4983     var url = this.env.comm_path; 
    4984  
    4985     // overwrite task name 
    4986     if (action.match(/([a-z]+)\/([a-z-_]+)/)) { 
    4987       action = RegExp.$2; 
    4988       url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); 
    4989     } 
     5178    var url = this.url(action, query); 
    49905179 
    49915180    // trigger plugin hook 
     
    50005189    } 
    50015190 
    5002     url += '&_remote=1&_action=' + action + (query ? '&' : '') + query; 
     5191    url += '&_remote=1'; 
    50035192 
    50045193    // send request 
     
    50145203  this.http_post = function(action, postdata, lock) 
    50155204  { 
    5016     var url = this.env.comm_path; 
    5017  
    5018     // overwrite task name 
    5019     if (action.match(/([a-z]+)\/([a-z-_]+)/)) { 
    5020       action = RegExp.$2; 
    5021       url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); 
    5022     } 
    5023  
    5024     url += '&_action=' + action; 
     5205    var url = this.url(action); 
    50255206 
    50265207    if (postdata && typeof(postdata) == 'object') { 
     
    51695350  }; 
    51705351 
     5352  // post the given form to a hidden iframe 
     5353  this.async_upload_form = function(form, action, onload) 
     5354  { 
     5355    var ts = new Date().getTime(); 
     5356    var frame_name = 'rcmupload'+ts; 
     5357 
     5358    // have to do it this way for IE 
     5359    // otherwise the form will be posted to a new window 
     5360    if (document.all) { 
     5361      var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>'; 
     5362      document.body.insertAdjacentHTML('BeforeEnd', html); 
     5363    } 
     5364    else { // for standards-compilant browsers 
     5365      var frame = document.createElement('iframe'); 
     5366      frame.name = frame_name; 
     5367      frame.style.border = 'none'; 
     5368      frame.style.width = 0; 
     5369      frame.style.height = 0; 
     5370      frame.style.visibility = 'hidden'; 
     5371      document.body.appendChild(frame); 
     5372    } 
     5373 
     5374    // handle upload errors, parsing iframe content in onload 
     5375    $(frame_name).bind('load', {ts:ts}, onload); 
     5376 
     5377    form.target = frame_name; 
     5378    form.action = this.url(action, { _uploadid:ts }); 
     5379    form.setAttribute('enctype', 'multipart/form-data'); 
     5380    form.submit(); 
     5381  }; 
     5382   
    51715383  // starts interval for keep-alive/check-recent signal 
    51725384  this.start_keepalive = function() 
  • trunk/roundcubemail/program/localization/en_US/labels.inc

    r4410 r4424  
    244244 
    245245// address boook 
    246 $labels['name']      = 'Display name'; 
    247 $labels['firstname'] = 'First name'; 
    248 $labels['surname']   = 'Last name'; 
    249 $labels['email']     = 'E-Mail'; 
    250  
     246$labels['name']         = 'Display name'; 
     247$labels['firstname']    = 'First name'; 
     248$labels['surname']      = 'Last name'; 
     249$labels['middlename']   = 'Middle name'; 
     250$labels['nameprefix']   = 'Prefix'; 
     251$labels['namesuffix']   = 'Suffix'; 
     252$labels['nickname']     = 'Nickname'; 
     253$labels['jobtitle']     = 'Job title'; 
     254$labels['organization'] = 'Company'; 
     255$labels['department']   = 'Department'; 
     256$labels['gender']       = 'Gender'; 
     257$labels['maidenname']   = 'Maiden name'; 
     258$labels['email']        = 'E-Mail'; 
     259$labels['phone']        = 'Phone'; 
     260$labels['address']      = 'Address'; 
     261$labels['street']       = 'Street'; 
     262$labels['locality']     = 'City'; 
     263$labels['zipcode']      = 'Zip code'; 
     264$labels['region']       = 'Region'; 
     265$labels['country']      = 'Country'; 
     266$labels['birthday']     = 'Birthday'; 
     267$labels['anniversary']  = 'Anniversary'; 
     268$labels['website']      = 'Website'; 
     269$labels['instantmessenger'] = 'IM'; 
     270$labels['notes'] = 'Notes'; 
     271$labels['male']   = 'male'; 
     272$labels['female'] = 'female'; 
     273$labels['manager'] = 'Manager'; 
     274$labels['assistant'] = 'Assistant'; 
     275$labels['spouse'] = 'Spouse'; 
     276 
     277$labels['addfield'] = 'Add field...'; 
    251278$labels['addcontact'] = 'Add new contact'; 
    252279$labels['editcontact'] = 'Edit contact'; 
     
    259286$labels['delete'] = 'Delete'; 
    260287$labels['rename'] = 'Rename'; 
     288$labels['addphoto'] = 'Add'; 
     289$labels['replacephoto'] = 'Replace'; 
    261290 
    262291$labels['newcontact']     = 'Create new contact card'; 
  • trunk/roundcubemail/program/localization/en_US/messages.inc

    r4410 r4424  
    105105$messages['addresswriterror'] = 'The selected address book is not writeable'; 
    106106$messages['contactaddedtogroup'] = 'Successfully added the contacts to this group'; 
    107 $messages['contactremovedfromgroup'] = 'Successfully remove contacts from this group'; 
     107$messages['contactremovedfromgroup'] = 'Successfully removed contacts from this group'; 
    108108$messages['importwait'] = 'Importing, please wait...'; 
    109109$messages['importerror'] = 'Import failed! The uploaded file is not a valid vCard file.'; 
     
    138138$messages['folderupdated'] = 'Folder updated successfully'; 
    139139$messages['foldercreated'] = 'Folder created successfully'; 
     140$messages['invalidimageformat'] = 'Not a valid image format'; 
    140141 
    141142?> 
  • trunk/roundcubemail/program/steps/addressbook/copy.inc

    r4410 r4424  
    4949 
    5050        if (!$plugin['abort']) { 
    51           if ($insert_id = $TARGET->insert($a_record, false)) { 
     51          if ($insert_id = $TARGET->insert($plugin['record'], false)) { 
    5252            $ids[] = $insert_id; 
    5353            $success++; 
  • trunk/roundcubemail/program/steps/addressbook/delete.inc

    r4410 r4424  
    3939        $result = $CONTACTS->count(); 
    4040 
     41        // update saved search after data changed 
     42        if (($search_request = $_REQUEST['_search']) && isset($_SESSION['search'][$search_request])) 
     43            $_SESSION['search'][$search_request] = $CONTACTS->refresh_search(); 
     44 
    4145        // update message count display 
    4246        $OUTPUT->set_env('pagecount', ceil($result->count / $CONTACTS->page_size)); 
  • trunk/roundcubemail/program/steps/addressbook/edit.inc

    r4410 r4424  
    3232 
    3333 
    34 function rcmail_contact_editform($attrib) 
    35 { 
    36    global $RCMAIL, $CONTACTS, $OUTPUT; 
     34function rcmail_contact_edithead($attrib) 
     35{ 
     36   global $RCMAIL, $CONTACTS; 
    3737 
    3838    // check if we have a valid result 
     
    4040        && !(($result = $CONTACTS->get_result()) && ($record = $result->first())) 
    4141    ) { 
    42         $OUTPUT->show_message('contactnotfound'); 
     42        $RCMAIL->output->show_message('contactnotfound'); 
    4343        return false; 
    4444    } 
     45     
     46    $i_size = !empty($attrib['size']) ? $attrib['size'] : 20; 
     47 
     48    $form = array( 
     49        'head' => array( 
     50            'content' => array( 
     51                'prefix' => array('size' => $i_size), 
     52                'firstname' => array('size' => $i_size, 'visible' => true), 
     53                'middlename' => array('size' => $i_size), 
     54                'surname' => array('size' => $i_size, 'visible' => true), 
     55                'suffix' => array('size' => $i_size), 
     56                'name' => array('size' => 2*$i_size), 
     57                'nickname' => array('size' => 2*$i_size), 
     58                'company' => array('size' => $i_size), 
     59                'department' => array('size' => $i_size), 
     60                'jobtitle' => array('size' => $i_size), 
     61            ) 
     62        ) 
     63    ); 
     64 
     65    list($form_start, $form_end) = get_form_tags($attrib); 
     66    unset($attrib['form'], $attrib['name'], $attrib['size']); 
     67 
     68    // return the address edit form 
     69    $out = rcmail_contact_form($form, $record, $attrib); 
     70 
     71    return $form_start . $out . $form_end; 
     72} 
     73 
     74 
     75function rcmail_contact_editform($attrib) 
     76{ 
     77   global $RCMAIL, $CONTACTS, $CONTACT_COLTYPES; 
     78 
     79    // check if we have a valid result 
     80    if ($RCMAIL->action != 'add' 
     81        && !(($result = $CONTACTS->get_result()) && ($record = $result->first())) 
     82    ) { 
     83        $RCMAIL->output->show_message('contactnotfound'); 
     84        return false; 
     85    } 
    4586 
    4687    // add some labels to client 
    47     $OUTPUT->add_label('noemailwarning', 'nonamewarning'); 
     88    $RCMAIL->output->add_label('noemailwarning', 'nonamewarning'); 
    4889 
    4990    $i_size = !empty($attrib['size']) ? $attrib['size'] : 40; 
    50     $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 6; 
     91    $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 10; 
    5192    $t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40; 
    5293 
     
    5596            'name'    => rcube_label('contactproperties'), 
    5697            'content' => array( 
    57                 'name' => array('type' => 'text', 'size' => $i_size), 
    58                 'firstname' => array('type' => 'text', 'size' => $i_size), 
    59                 'surname' => array('type' => 'text', 'size' => $i_size), 
    60                 'email' => array('type' => 'text', 'size' => $i_size), 
     98                'gender' => array('visible' => false), 
     99                'maidenname' => array('size' => $i_size), 
     100                'email' => array('size' => $i_size, 'visible' => true), 
     101                'phone' => array('size' => $i_size, 'visible' => true), 
     102                'address' => array('visible' => true), 
     103                'birthday' => array('size' => 12), 
     104                'anniversary' => array('size' => $i_size), 
     105                'website' => array('size' => $i_size), 
     106                'im' => array('size' => $i_size), 
     107                'manager' => array('size' => $i_size), 
     108                'assistant' => array('size' => $i_size), 
     109                'spouse' => array('size' => $i_size), 
    61110            ), 
    62111        ), 
    63112    ); 
    64  
     113     
     114    if (isset($CONTACT_COLTYPES['notes'])) { 
     115        $form['notes'] = array( 
     116            'name'    => rcube_label('notes'), 
     117            'content' => array( 
     118                'notes' => array('size' => $t_cols, 'rows' => $t_rows, 'label' => false, 'visible' => true, 'limit' => 1), 
     119            ), 
     120            'single' => true, 
     121        ); 
     122    } 
    65123 
    66124    list($form_start, $form_end) = get_form_tags($attrib); 
     
    68126 
    69127    // return the complete address edit form as table 
    70     $out = rcmail_contact_form($form, $record); 
     128    $out = rcmail_contact_form($form, $record, $attrib); 
    71129 
    72130    return $form_start . $out . $form_end; 
     131} 
     132 
     133 
     134function rcmail_upload_photo_form($attrib) 
     135{ 
     136  global $OUTPUT; 
     137 
     138  // add ID if not given 
     139  if (!$attrib['id']) 
     140    $attrib['id'] = 'rcmUploadbox'; 
     141 
     142  // find max filesize value 
     143  $max_filesize = parse_bytes(ini_get('upload_max_filesize')); 
     144  $max_postsize = parse_bytes(ini_get('post_max_size')); 
     145  if ($max_postsize && $max_postsize < $max_filesize) 
     146    $max_filesize = $max_postsize; 
     147  $max_filesize = show_bytes($max_filesize); 
     148   
     149  $hidden = new html_hiddenfield(array('name' => '_cid', 'value' => $GLOBALS['cid'])); 
     150  $input = new html_inputfield(array('type' => 'file', 'name' => '_photo', 'size' => $attrib['size'])); 
     151  $button = new html_inputfield(array('type' => 'button')); 
     152   
     153  $out = html::div($attrib, 
     154    $OUTPUT->form_tag(array('name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'), 
     155      $hidden->show() . 
     156      html::div(null, $input->show()) . 
     157      html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) . 
     158      html::div('buttons', 
     159        $button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' . 
     160        $button->show(rcube_label('upload'), array('class' => 'button mainaction', 'onclick' => JS_OBJECT_NAME . ".command('upload-photo', this.form)")) 
     161      ) 
     162    ) 
     163  ); 
     164   
     165  $OUTPUT->add_label('addphoto','replacephoto'); 
     166  $OUTPUT->add_gui_object('uploadbox', $attrib['id']); 
     167  return $out; 
    73168} 
    74169 
     
    104199 
    105200 
     201$OUTPUT->add_handler('contactedithead', 'rcmail_contact_edithead'); 
    106202$OUTPUT->add_handler('contacteditform', 'rcmail_contact_editform'); 
     203$OUTPUT->add_handler('contactphoto',    'rcmail_contact_photo'); 
     204$OUTPUT->add_handler('photouploadform', 'rcmail_upload_photo_form'); 
    107205 
    108206if (!$CONTACTS->get_result() && $OUTPUT->template_exists('contactadd')) 
  • trunk/roundcubemail/program/steps/addressbook/export.inc

    r4410 r4424  
    3131 
    3232while ($result && ($row = $result->next())) { 
    33   $vcard = new rcube_vcard($row['vcard']); 
    34   $vcard->set('displayname', $row['name']); 
    35   $vcard->set('firstname', $row['firstname']); 
    36   $vcard->set('surname', $row['surname']); 
    37   $vcard->set('email', $row['email']); 
    38    
    39   echo $vcard->export(); 
     33  // we already have a vcard record 
     34  if ($row['vcard']) { 
     35    echo $row['vcard']; 
     36  } 
     37  // copy values into vcard object 
     38  else { 
     39    $vcard = new rcube_vcard($row['vcard']); 
     40    $vcard->reset(); 
     41    foreach ($row as $key => $values) { 
     42      list($field, $section) = explode(':', $key); 
     43      foreach ((array)$values as $value) { 
     44        if (is_array($value) || strlen($value)) 
     45          $vcard->set($field, $value, strtoupper($section)); 
     46      } 
     47    } 
     48     
     49    echo $vcard->export(); 
     50  } 
    4051} 
    4152 
  • trunk/roundcubemail/program/steps/addressbook/func.inc

    r4419 r4424  
    5757 
    5858 
     59// general definition of contact coltypes 
     60$CONTACT_COLTYPES = array( 
     61  'name'         => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('name')), 
     62  'firstname'    => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('firstname')), 
     63  'surname'      => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('surname')), 
     64  'middlename'   => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('middlename')), 
     65  'prefix'       => array('type' => 'text', 'size' => 8,  'limit' => 1, 'label' => rcube_label('nameprefix')), 
     66  'suffix'       => array('type' => 'text', 'size' => 8,  'limit' => 1, 'label' => rcube_label('namesuffix')), 
     67  'nickname'     => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('nickname')), 
     68  'jobtitle'     => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('jobtitle')), 
     69  'organization' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('organization')), 
     70  'department'   => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('department')), 
     71  'gender'       => array('type' => 'select', 'limit' => 1, 'label' => rcube_label('gender'), 'options' => array('male' => rcube_label('male'), 'female' => rcube_label('female'))), 
     72  'maidenname'   => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('maidenname')), 
     73  'email'        => array('type' => 'text', 'size' => 40, 'label' => rcube_label('email'), 'subtypes' => array('home','work','other')), 
     74  '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')), 
     75  'address'      => array('type' => 'composite', 'label' => rcube_label('address'), 'subtypes' => array('home','work','other'), 'childs' => array( 
     76    'street'     => array('type' => 'text', 'size' => 40, 'label' => rcube_label('street')), 
     77    'locality'   => array('type' => 'text', 'size' => 28, 'label' => rcube_label('locality')), 
     78    'zipcode'    => array('type' => 'text', 'size' => 8, 'label' => rcube_label('zipcode')), 
     79    'region'     => array('type' => 'text', 'size' => 12, 'label' => rcube_label('region')), 
     80    'country'    => array('type' => 'text', 'size' => 40, 'label' => rcube_label('country')), 
     81  )), 
     82  'birthday'     => array('type' => 'date', 'size' => 12, 'label' => rcube_label('birthday'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col'), 
     83  'anniversary'  => array('type' => 'date', 'size' => 12, 'label' => rcube_label('anniversary'), 'limit' => 1, 'render_func' => 'rcmail_format_date_col'), 
     84  'website'      => array('type' => 'text', 'size' => 40, 'label' => rcube_label('website'), 'subtypes' => array('homepage','work','blog','other')), 
     85  'im'           => array('type' => 'text', 'size' => 40, 'label' => rcube_label('instantmessenger'), 'subtypes' => array('aim','icq','msn','yahoo','jabber','skype','other')), 
     86  'notes'        => array('type' => 'textarea', 'size' => 40, 'rows' => 15, 'label' => rcube_label('notes'), 'limit' => 1), 
     87  'photo'        => array('type' => 'image', 'limit' => 1), 
     88  'assistant'    => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('assistant')), 
     89  'manager'      => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('manager')), 
     90  'spouse'       => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('spouse')), 
     91  // TODO: define fields for vcards like GEO, KEY 
     92); 
     93 
     94// reduce/extend $CONTACT_COLTYPES with specification from the current $CONTACT object 
     95if (is_array($CONTACTS->coltypes)) { 
     96    // remove cols not listed by the backend class 
     97    $contact_cols = $CONTACTS->coltypes[0] ? array_flip($CONTACTS->coltypes) : $CONTACTS->coltypes; 
     98    $CONTACT_COLTYPES = array_intersect_key($CONTACT_COLTYPES, $contact_cols); 
     99    // add associative coltypes definition 
     100    if (!$CONTACTS->coltypes[0]) { 
     101        foreach ($CONTACTS->coltypes as $col => $colprop) 
     102            $CONTACT_COLTYPES[$col] = $CONTACT_COLTYPES[$col] ? array_merge($CONTACT_COLTYPES[$col], $colprop) : $colprop; 
     103    } 
     104} 
     105 
     106$OUTPUT->set_env('photocol', is_array($CONTACT_COLTYPES['photo'])); 
     107 
     108 
    59109function rcmail_directory_list($attrib) 
    60110{ 
     
    73123            'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s')); 
    74124 
    75     if (!$current && strtolower($RCMAIL->config->get('address_book_type', 'sql')) != 'ldap') { 
    76         $current = '0'; 
    77     } 
    78     else if (!$current) { 
    79         // DB address book not used, see if a source is set, if not use the 
    80         // first LDAP directory. 
    81         $current = key((array)$RCMAIL->config->get('ldap_public', array())); 
    82     } 
     125    // currently selected is the first address source in the list 
     126    if (!isset($current)) 
     127        $current = strval(key((array)$OUTPUT->env['address_sources'])); 
    83128 
    84129    foreach ((array)$OUTPUT->env['address_sources'] as $j => $source) { 
    85         $id = $source['id'] ? $source['id'] : $j; 
     130        $id = strval($source['id'] ? $source['id'] : $j); 
    86131        $js_id = JQ($id); 
    87         $dom_id = preg_replace('/[^a-z0-9\-_]/i', '', $id); 
    88         $out .= sprintf($line_templ, $dom_id, ($current == $id ? 'selected' : ''), 
     132        $dom_id = preg_replace('/[^a-z0-9\-_]/i', '_', $id); 
     133        $out .= sprintf($line_templ, $dom_id, ($current === $id ? 'selected' : ''), 
    89134            Q(rcmail_url(null, array('_source' => $id))), 
    90135            $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id))); 
    91         $groupdata = rcmail_contact_groups(array('out' => $out, 'jsdata' => $jsdata, 'source' => $id)); 
     136 
     137        $groupdata = array('out' => $out, 'jsdata' => $jsdata, 'source' => $id); 
     138        if ($source['groups']) 
     139            $groupdata = rcmail_contact_groups($groupdata); 
    92140        $jsdata = $groupdata['jsdata']; 
    93141        $out = $groupdata['out']; 
     
    131179    global $CONTACTS, $OUTPUT; 
    132180 
     181    // define list of cols to be displayed 
     182    $a_show_cols = array('name'); 
     183 
    133184    // count contacts for this user 
    134     $result = $CONTACTS->list_records(); 
     185    $result = $CONTACTS->list_records($a_show_cols); 
    135186 
    136187    // add id to message list table if not specified 
    137188    if (!strlen($attrib['id'])) 
    138189        $attrib['id'] = 'rcmAddressList'; 
    139  
    140     // define list of cols to be displayed 
    141     $a_show_cols = array('name'); 
    142190 
    143191    // create XHTML table 
     
    234282 
    235283 
    236 function rcmail_contact_form($form, $record) 
    237 { 
    238     global $RCMAIL; 
     284function rcmail_contact_form($form, $record, $attrib = null) 
     285{ 
     286    global $RCMAIL, $CONFIG; 
    239287 
    240288    // Allow plugins to modify contact form content 
     
    244292    $form = $plugin['form']; 
    245293    $record = $plugin['record']; 
     294    $edit_mode = $RCMAIL->action != 'show'; 
     295    $del_button = $attrib['deleteicon'] ? html::img(array('src' => $CONFIG['skin_path'] . $attrib['deleteicon'], 'alt' => rcube_label('delete'))) : rcube_label('delete'); 
     296    unset($attrib['deleteicon']); 
    246297    $out = ''; 
    247  
    248     foreach ($form as $fieldset) { 
     298     
     299    // get default coltypes 
     300    $coltypes = $GLOBALS['CONTACT_COLTYPES']; 
     301    $coltype_lables = array(); 
     302     
     303    foreach ($coltypes as $col => $prop) { 
     304        if ($prop['subtypes']) { 
     305            $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype')); 
     306            $select_subtype->add($prop['subtypes']); 
     307            $coltypes[$col]['subtypes_select'] = $select_subtype->show(); 
     308        } 
     309        if ($prop['childs']) { 
     310            foreach ($prop['childs'] as $childcol => $cp) 
     311                $coltype_lables[$childcol] = array('label' => $cp['label']); 
     312        } 
     313    } 
     314 
     315    foreach ($form as $section => $fieldset) { 
     316        // skip empty sections 
    249317        if (empty($fieldset['content'])) 
    250318            continue; 
    251319 
     320        $select_add = new html_select(array('class' => 'addfieldmenu', 'rel' => $section)); 
     321        $select_add->add(rcube_label('addfield'), ''); 
     322 
     323        // render head section with name fields (not a regular list of rows) 
     324        if ($section == 'head') { 
     325            $content = ''; 
     326             
     327            $names_arr = array($record['prefix'], $record['firstname'], $record['middlename'], $record['surname'], $record['suffix']); 
     328            if ($record['name'] == join(' ', array_filter($names_arr))) 
     329              unset($record['name']); 
     330 
     331            // group fields 
     332            $field_blocks = array( 
     333                'names'    => array('prefix','firstname','middlename','surname','suffix'), 
     334                'displayname' => array('name'), 
     335                'nickname' => array('nickname'), 
     336                'jobnames' => array('organization','department','jobtitle'), 
     337            ); 
     338            foreach ($field_blocks as $blockname => $colnames) { 
     339                $fields = ''; 
     340                foreach ($colnames as $col) { 
     341                    // skip cols unknown to the backend 
     342                    if (!$coltypes[$col]) 
     343                        continue; 
     344 
     345                    if ($RCMAIL->action == 'show') { 
     346                        if (!empty($record[$col])) 
     347                            $fields .= html::span('namefield ' . $col, Q($record[$col])) . " "; 
     348                    } 
     349                    else { 
     350                        $colprop = (array)$fieldset['content'][$col] + (array)$coltypes[$col]; 
     351                        $colprop['id'] = 'ff_'.$col; 
     352                        if (empty($record[$col]) && !$colprop['visible']) { 
     353                            $colprop['style'] = 'display:none'; 
     354                            $select_add->add($colprop['label'], $col); 
     355                        } 
     356                        $fields .= rcmail_get_edit_field($col, $record[$col], $colprop, $colprop['type']); 
     357                    } 
     358                } 
     359                $content .= html::div($blockname, $fields); 
     360            } 
     361             
     362            if ($edit_mode) 
     363                $content .= html::p('addfield', $select_add->show(null)); 
     364 
     365            $out .= html::tag('fieldset', $attrib, (!empty($fieldset['name']) ? html::tag('legend', null, Q($fieldset['name'])) : '') . $content) ."\n"; 
     366            continue; 
     367        } 
     368 
    252369        $content = ''; 
    253370        if (is_array($fieldset['content'])) { 
    254             $table = new html_table(array('cols' => 2)); 
    255  
    256371            foreach ($fieldset['content'] as $col => $colprop) { 
    257                 $colprop['id'] = 'rcmfd_'.$col; 
    258  
    259                 $label = !empty($colprop['label']) ? $colprop['label'] : rcube_label($col); 
     372                // remove subtype part of col name 
     373                list($field, $subtype) = explode(':', $col); 
     374                if (!$subtype) $subtype = 'home'; 
     375                $fullkey = $col.':'.$subtype; 
     376 
     377                // skip cols unknown to the backend 
     378                if (!$coltypes[$field]) 
     379                    continue; 
     380 
     381                // merge colprop with global coltype configuration 
     382                $colprop += $coltypes[$field]; 
     383                $label = isset($colprop['label']) ? $colprop['label'] : rcube_label($col); 
     384 
     385                // prepare subtype selector in edit mode 
     386                if ($edit_mode && is_array($colprop['subtypes'])) { 
     387                    $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype')); 
     388                    $select_subtype->add($colprop['subtypes']); 
     389                } 
     390                else 
     391                    $select_subtype = null; 
    260392 
    261393                if (!empty($colprop['value'])) { 
    262                     $value = $colprop['value']; 
    263                 } 
    264                 else if ($RCMAIL->action == 'show') { 
    265                     $value = $record[$col]; 
     394                    $values = (array)$colprop['value']; 
    266395                } 
    267396                else { 
    268                     $value = rcmail_get_edit_field($col, $record[$col], $colprop, $colprop['type']); 
     397                    // iterate over possible subtypes and collect values with their subtype 
     398                    if (is_array($colprop['subtypes'])) { 
     399                        $values = $subtypes = array(); 
     400                        foreach ($colprop['subtypes'] as $i => $st) { 
     401                            $newval = false; 
     402                            if ($record[$field.':'.$st]) { 
     403                                $subtypes[count($values)] = $st; 
     404                                $newval = $record[$field.':'.$st]; 
     405                            } 
     406                            else if ($i == 0 && $record[$field]) { 
     407                                $subtypes[count($values)] = $st; 
     408                                $newval = $record[$field]; 
     409                            } 
     410                            if ($newval !== false) { 
     411                                if (is_array($newval) && isset($newval[0])) 
     412                                    $values = array_merge($values, $newval); 
     413                                else 
     414                                    $values[] = $newval; 
     415                            } 
     416                        } 
     417                    } 
     418                    else { 
     419                        $values = $record[$fullkey] ? $record[$fullkey] : $record[$field]; 
     420                        $subtypes = null; 
     421                    } 
    269422                } 
    270423 
    271                 $table->add('title', sprintf('<label for="%s">%s</label>', $colprop['id'], Q($label))); 
    272                 $table->add(null, $value); 
     424                // hack: create empty values array to force this field to be displayed 
     425                if (empty($values) && $colprop['visible']) 
     426                    $values[] = ''; 
     427 
     428                $rows = ''; 
     429                foreach ((array)$values as $i => $val) { 
     430                    if ($subtypes[$i]) 
     431                        $subtype = $subtypes[$i]; 
     432 
     433                    // render composite field 
     434                    if ($colprop['type'] == 'composite') { 
     435                        $composite = array(); $j = 0; 
     436                        $template = $RCMAIL->config->get($col . '_template', '{'.join('} {', array_keys($colprop['childs'])).'}'); 
     437                        foreach ($colprop['childs'] as $childcol => $cp) { 
     438                            $childvalue = $val[$childcol] ? $val[$childcol] : $val[$j]; 
     439 
     440                            if ($edit_mode) { 
     441                                if ($colprop['subtypes'] || $colprop['limit'] != 1) $cp['array'] = true; 
     442                                $composite['{'.$childcol.'}'] = rcmail_get_edit_field($childcol, $childvalue, $cp, $cp['type']) . " "; 
     443                            } 
     444                            else { 
     445                                $childval = $cp['render_func'] ? call_user_func($cp['render_func'], $childvalue, $childcol) : Q($childvalue); 
     446                                $composite['{'.$childcol.'}'] = html::span('data ' . $childcol, $childval) . " "; 
     447                            } 
     448                            $j++; 
     449                        } 
     450 
     451                        $coltypes[$field] += (array)$colprop; 
     452                        $coltypes[$field]['count']++; 
     453                        $val = strtr($template, $composite); 
     454                    } 
     455                    else if ($edit_mode) { 
     456                        // call callback to render/format value 
     457                        if ($colprop['render_func']) 
     458                            $val = call_user_func($colprop['render_func'], $val, $col); 
     459 
     460                        $coltypes[$field] = (array)$colprop + $coltypes[$field]; 
     461 
     462                        if ($colprop['subtypes'] || $colprop['limit'] != 1) 
     463                            $colprop['array'] = true; 
     464 
     465                        $val = rcmail_get_edit_field($col, $val, $colprop, $colprop['type']); 
     466                        $coltypes[$field]['count']++; 
     467                    } 
     468                    else if ($colprop['render_func']) 
     469                        $val = call_user_func($colprop['render_func'], $val, $col); 
     470                    else if (is_array($colprop['options']) && isset($colprop['options'][$val])) 
     471                        $val = $colprop['options'][$val]; 
     472                    else 
     473                        $val = Q($val); 
     474 
     475                    // use subtype as label 
     476                    if ($colprop['subtypes']) 
     477                        $label = $subtype; 
     478 
     479                    // add delete button/link 
     480                    if ($edit_mode && !($colprop['visible'] && $colprop['limit'] == 1)) 
     481                        $val .= html::a(array('href' => '#del', 'class' => 'contactfieldbutton deletebutton', 'title' => rcube_label('delete'), 'rel' => $col), $del_button); 
     482 
     483                    // display row with label 
     484                    if ($label) { 
     485                        $rows .= html::div('row', 
     486                            html::div('contactfieldlabel label', $select_subtype ? $select_subtype->show($subtype) : Q($label)) . 
     487                            html::div('contactfieldcontent '.$colprop['type'], $val)); 
     488                    } 
     489                    else   // row without label 
     490                        $rows .= html::div('row', html::div('contactfield', $val)); 
     491                } 
     492                 
     493                // add option to the add-field menu 
     494                if (!$colprop['limit'] || $coltypes[$field]['count'] < $colprop['limit']) { 
     495                    $select_add->add($colprop['label'], $col); 
     496                    $select_add->_count++; 
     497                } 
     498                 
     499                // wrap rows in fieldgroup container 
     500                $content .= html::tag('fieldset', array('class' => 'contactfieldgroup contactcontroller' . $col, 'style' => ($rows ? null : 'display:none')), 
     501                  ($colprop['subtypes'] ? html::tag('legend', null, Q($colprop['label'])) : ' ') . 
     502                  $rows); 
    273503            } 
    274             $content = $table->show(); 
     504 
     505            // also render add-field selector 
     506            if ($edit_mode) 
     507                $content .= html::p('addfield', $select_add->show(null, array('style' => $select_add->_count ? null : 'display:none'))); 
     508 
     509            $content = html::div(array('id' => 'contactsection' . $section), $content); 
    275510        } 
    276511        else { 
     
    280515        $out .= html::tag('fieldset', null, html::tag('legend', null, Q($fieldset['name'])) . $content) ."\n"; 
    281516    } 
     517 
     518    if ($edit_mode) { 
     519      $RCMAIL->output->set_env('coltypes', $coltypes + $coltype_lables); 
     520      $RCMAIL->output->set_env('delbutton', $del_button); 
     521      $RCMAIL->output->add_label('delete'); 
     522    } 
     523 
     524    return $out; 
     525} 
     526 
     527 
     528function rcmail_contact_photo($attrib) 
     529{ 
     530    global $CONTACTS, $CONTACT_COLTYPES, $RCMAIL, $CONFIG; 
     531     
     532    if ($result = $CONTACTS->get_result()) 
     533        $record = $result->first(); 
     534     
     535    $photo_img = $attrib['placeholder'] ? $CONFIG['skin_path'] . $attrib['placeholder'] : 'program/blank.gif'; 
     536    unset($attrib['placeholder']); 
     537     
     538    if ($CONTACT_COLTYPES['photo']) { 
     539        $RCMAIL->output->set_env('photo_placeholder', $photo_img); 
     540         
     541        if ($record['photo']) 
     542            $photo_img = $RCMAIL->url(array('_action' => 'photo', '_cid' => $record['ID'], '_source' => $_REQUEST['_source'])); 
     543        $img = html::img(array('src' => $photo_img, 'border' => 1, 'alt' => '')); 
     544        $content = html::div($attrib, $img); 
     545       
     546        if ($RCMAIL->action == 'edit' || $RCMAIL->action == 'add') { 
     547            $RCMAIL->output->add_gui_object('contactphoto', $attrib['id']); 
     548            $hidden = new html_hiddenfield(array('name' => '_photo', 'id' => 'ff_photo')); 
     549            $content .= $hidden->show(); 
     550        } 
     551  } 
    282552   
    283     return $out; 
     553  return $content; 
     554} 
     555 
     556 
     557function rcmail_format_date_col($val) 
     558{ 
     559    global $RCMAIL; 
     560    return format_date($val, $RCMAIL->config->get('date_format', 'Y-m-d')); 
    284561} 
    285562 
     
    298575$RCMAIL->register_action_map(array( 
    299576    'add' => 'edit.inc', 
     577    'photo' => 'show.inc', 
     578    'upload-photo' => 'save.inc', 
    300579    'group-create' => 'groups.inc', 
    301580    'group-rename' => 'groups.inc', 
  • trunk/roundcubemail/program/steps/addressbook/groups.inc

    r4410 r4424  
    8080  if ($created && $OUTPUT->ajax_call) { 
    8181    $OUTPUT->show_message('groupcreated', 'confirmation'); 
    82     $OUTPUT->command('insert_contact_group', array( 
    83       'source' => $source, 'id' => $created['id'], 'name' => $created['name'])); 
     82    $OUTPUT->command('insert_contact_group', array('source' => $source) + $created); 
    8483  } 
    8584  else if (!$created) { 
  • trunk/roundcubemail/program/steps/addressbook/import.inc

    r4410 r4424  
    151151      } 
    152152       
    153       $a_record = array( 
    154         'name' => $vcard->displayname, 
    155         'firstname' => $vcard->firstname, 
    156         'surname' => $vcard->surname, 
    157         'email' => $email, 
    158         'vcard' => $vcard->export(), 
    159       ); 
     153      $a_record = $vcard->get_assoc(); 
     154      $a_record['vcard'] = $vcard->export(); 
    160155       
    161156      $plugin = $RCMAIL->plugins->exec_hook('contact_create', array('record' => $a_record, 'source' => null)); 
  • trunk/roundcubemail/program/steps/addressbook/list.inc

    r4410 r4424  
    2121 
    2222// get contacts for this user 
    23 $result = $CONTACTS->list_records(); 
     23$result = $CONTACTS->list_records(array('name')); 
    2424 
    2525// update message count display 
  • trunk/roundcubemail/program/steps/addressbook/mailto.inc

    r4410 r4424  
    3030  $recipients = $CONTACTS->search($CONTACTS->primary_key, $cid); 
    3131 
    32   while (is_object($recipients) && ($rec = $recipients->iterate())) 
    33     $mailto[] = format_email_recipient($rec['email'], $rec['name']); 
     32  while (is_object($recipients) && ($rec = $recipients->iterate())) { 
     33    $emails = $CONTACTS->get_col_values('email', $rec, true); 
     34    $mailto[] = format_email_recipient($emails[0], $rec['name']); 
     35  } 
    3436} 
    3537 
  • trunk/roundcubemail/program/steps/addressbook/save.inc

    r4410 r4424  
    66 |                                                                       | 
    77 | This file is part of the Roundcube Webmail client                     | 
    8  | Copyright (C) 2005-2009, The Roundcube Dev Team                       | 
     8 | Copyright (C) 2005-2011, The Roundcube Dev Team                       | 
    99 | Licensed under the GNU GPL                                            | 
    1010 |                                                                       | 
     
    3030} 
    3131 
    32 // Basic input checks 
    33 if ((!get_input_value('_name', RCUBE_INPUT_POST) || !get_input_value('_email', RCUBE_INPUT_POST))) { 
     32 
     33// handle photo upload for contacts 
     34if ($RCMAIL->action == 'upload-photo') { 
     35    // clear all stored output properties (like scripts and env vars) 
     36    $OUTPUT->reset(); 
     37 
     38    if ($filepath = $_FILES['_photo']['tmp_name']) { 
     39        // check file type and resize image 
     40        $imageprop = rcmail::imageprops($_FILES['_photo']['tmp_name']); 
     41         
     42        if ($imageprop['width'] && $imageprop['height']) { 
     43            $maxsize = intval($RCMAIL->config->get('contact_photo_size', 160)); 
     44            $tmpfname = tempnam($RCMAIL->config->get('temp_dir'), 'rcmImgConvert'); 
     45            $save_hook = 'attachment_upload'; 
     46             
     47            // scale image to a maximum size 
     48            if (($imageprop['width'] > $maxsize || $imageprop['height'] > $maxsize) && 
     49                  (rcmail::imageconvert(array('in' => $filepath, 'out' => $tmpfname, 'size' => $maxsize.'x'.$maxsize, 'type' => $imageprop['type'])) !== false)) { 
     50                $filepath = $tmpfname; 
     51                $save_hook = 'attachment_save'; 
     52            } 
     53             
     54            // save uploaded file in storage backend 
     55            $attachment = $RCMAIL->plugins->exec_hook($save_hook, array( 
     56                'path' => $filepath, 
     57                'size' => $_FILES['_photo']['size'], 
     58                'name' => $_FILES['_photo']['name'], 
     59                'mimetype' => 'image/' . $imageprop['type'], 
     60            )); 
     61        } 
     62        else 
     63            $attachment['error'] = rcube_label('invalidimageformat'); 
     64 
     65        if ($attachment['status'] && !$attachment['abort']) { 
     66            $file_id = $attachment['id']; 
     67            $_SESSION['contacts']['files'][$file_id] = $attachment; 
     68            $OUTPUT->command('replace_contact_photo', $file_id); 
     69        } 
     70        else {  // upload failed 
     71            $err = $_FILES['_photo']['error']; 
     72            if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) 
     73                $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array('size' => show_bytes(parse_bytes(ini_get('upload_max_filesize')))))); 
     74            else if ($attachment['error']) 
     75                $msg = $attachment['error']; 
     76            else 
     77                $msg = rcube_label('fileuploaderror'); 
     78             
     79            $OUTPUT->command('display_message', $msg, 'error'); 
     80        } 
     81    } 
     82    else if ($_SERVER['REQUEST_METHOD'] == 'POST') { 
     83        // if filesize exceeds post_max_size then $_FILES array is empty, 
     84        // show filesizeerror instead of fileuploaderror 
     85        if ($maxsize = ini_get('post_max_size')) 
     86            $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array('size' => show_bytes(parse_bytes($maxsize))))); 
     87        else 
     88            $msg = rcube_label('fileuploaderror'); 
     89             
     90        $OUTPUT->command('display_message', $msg, 'error'); 
     91    } 
     92     
     93    $OUTPUT->command('photo_upload_end'); 
     94    $OUTPUT->send('iframe'); 
     95} 
     96 
     97 
     98// read POST values into hash array 
     99$a_record = array(); 
     100foreach ($GLOBALS['CONTACT_COLTYPES'] as $col => $colprop) { 
     101  $fname = '_'.$col; 
     102  if ($colprop['composite']) 
     103    continue; 
     104  // gather form data of composite fields 
     105  if ($colprop['childs']) { 
     106    $values = array(); 
     107    foreach ($colprop['childs'] as $childcol => $cp) { 
     108      $vals = get_input_value('_'.$childcol, RCUBE_INPUT_POST); 
     109      foreach ((array)$vals as $i => $val) 
     110        $values[$i][$childcol] = $val; 
     111    } 
     112    $subtypes = get_input_value('_subtype_' . $col, RCUBE_INPUT_POST); 
     113    foreach ($subtypes as $i => $subtype) 
     114      if ($values[$i]) 
     115        $a_record[$col.':'.$subtype][] = $values[$i]; 
     116  } 
     117  // assign values and subtypes 
     118  else if (is_array($_POST[$fname])) { 
     119    $values = get_input_value($fname, RCUBE_INPUT_POST); 
     120    $subtypes = get_input_value('_subtype_' . $col, RCUBE_INPUT_POST); 
     121    foreach ($values as $i => $val) { 
     122      $subtype = $subtypes[$i] ? ':'.$subtypes[$i] : ''; 
     123      $a_record[$col.$subtype][] = $val; 
     124    } 
     125  } 
     126  else if (isset($_POST[$fname])) { 
     127    $a_record[$col] = get_input_value($fname, RCUBE_INPUT_POST); 
     128  } 
     129} 
     130 
     131if (empty($a_record['name'])) 
     132  $a_record['name'] = join(' ', array_filter(array($a_record['prefix'], $a_record['firstname'], $a_record['middlename'], $a_record['surname'], $a_record['suffix'],))); 
     133 
     134#var_dump($a_record); 
     135 
     136// Basic input checks (TODO: delegate to $CONTACTS instance) 
     137if (empty($a_record['name'])/* || empty($a_record['email'])*/) { 
    34138  $OUTPUT->show_message('formincomplete', 'warning'); 
    35139  rcmail_overwrite_action($return_action); 
     
    37141} 
    38142 
    39  
    40 // setup some vars we need 
    41 $a_save_cols = array('name', 'firstname', 'surname', 'email'); 
    42 $a_record = array(); 
    43  
    44 // read POST values into hash array 
    45 foreach ($a_save_cols as $col) { 
    46   $fname = '_'.$col; 
    47   if (isset($_POST[$fname])) 
    48     $a_record[$col] = get_input_value($fname, RCUBE_INPUT_POST); 
    49 } 
    50  
    51143// Validity checks 
    52 $_email = idn_to_ascii($a_record['email']); 
    53 if (!check_email($_email, false)) { 
    54   $OUTPUT->show_message('emailformaterror', 'warning', array('email' => $_email)); 
    55   rcmail_overwrite_action($return_action); 
    56   return; 
     144foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) { 
     145  if (strlen($email)) { 
     146    $_email = idn_to_ascii($email); 
     147    if (!check_email($_email, false)) { 
     148      $OUTPUT->show_message('emailformaterror', 'warning', array('email' => $email)); 
     149      rcmail_overwrite_action($return_action); 
     150      return; 
     151    } 
     152  } 
     153} 
     154 
     155// get raw photo data if changed 
     156if (isset($a_record['photo'])) { 
     157    if ($a_record['photo'] == '-del-') { 
     158        $a_record['photo'] = ''; 
     159    } 
     160    else if ($tempfile = $_SESSION['contacts']['files'][$a_record['photo']]) { 
     161        $tempfile = $RCMAIL->plugins->exec_hook('attachment_get', $tempfile); 
     162        if ($tempfile['status']) 
     163            $a_record['photo'] = $tempfile['data'] ? $tempfile['data'] : @file_get_contents($tempfile['path']); 
     164    } 
     165    else 
     166        unset($a_record['photo']); 
     167     
     168    // cleanup session data 
     169    $RCMAIL->plugins->exec_hook('attachments_cleanup', array()); 
     170    $RCMAIL->session->remove('contacts'); 
    57171} 
    58172 
     
    93207  else { 
    94208    // show error message 
    95     $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error', null, false); 
     209    $err = $CONTACTS->get_error(); 
     210    $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : ($err['message'] ? $err['message'] : 'errorsaving'), 'error', null, false); 
    96211    rcmail_overwrite_action('show'); 
    97212  } 
     
    101216else { 
    102217  // check for existing contacts 
    103   $existing = $CONTACTS->search('email', $a_record['email'], true, false); 
     218  $existing = false; 
     219  foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) { 
     220      if (($res = $CONTACTS->search('email', $email, true, false)) && $res->count) { 
     221          $existing = true; 
     222          break; 
     223      } 
     224  } 
    104225 
    105226  // show warning message 
    106   if ($existing->count) { 
     227  if ($existing) { 
    107228    $OUTPUT->show_message('contactexists', 'warning', null, false); 
    108229    rcmail_overwrite_action('add'); 
     
    139260  else { 
    140261    // show error message 
    141     $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsaving', 'error', null, false); 
     262    $err = $CONTACTS->get_error(); 
     263    $OUTPUT->show_message($plugin['message'] ? $plugin['message'] : ($err['message'] ? $err['message'] : 'errorsaving'), 'error', null, false); 
    142264    rcmail_overwrite_action('add'); 
    143265  } 
  • trunk/roundcubemail/program/steps/addressbook/search.inc

    r4410 r4424  
    66 |                                                                       | 
    77 | This file is part of the Roundcube Webmail client                     | 
    8  | Copyright (C) 2005-2007, The Roundcube Dev Team                       | 
     8 | Copyright (C) 2005-2011, The Roundcube Dev Team                       | 
    99 | Licensed under the GNU GPL                                            | 
    1010 |                                                                       | 
     
    2929$result = $CONTACTS->search(array('name','email'), $search); 
    3030 
     31// save search settings in session 
     32$_SESSION['search'][$search_request] = $CONTACTS->get_search_set(); 
     33 
    3134if ($result->count > 0) 
    3235{ 
    33   // save search settings in session 
    34   $_SESSION['search'][$search_request] = $CONTACTS->get_search_set(); 
    35  
    3636  // create javascript list 
    3737  rcmail_js_contacts_list($result); 
  • trunk/roundcubemail/program/steps/addressbook/show.inc

    r4410 r4424  
    2626} 
    2727 
     28// return raw photo of the given contact 
     29if ($RCMAIL->action == 'photo') { 
     30    if (($file_id = get_input_value('_photo', RCUBE_INPUT_GPC)) && ($tempfile = $_SESSION['contacts']['files'][$file_id])) { 
     31        $tempfile = $RCMAIL->plugins->exec_hook('attachment_display', $tempfile); 
     32        if ($tempfile['status']) { 
     33            if ($tempfile['data']) 
     34                $data = $tempfile['data']; 
     35            else if ($tempfile['path']) 
     36                $data = file_get_contents($tempfile['path']); 
     37        } 
     38    } 
     39    else if ($record['photo']) { 
     40        $data = is_array($record['photo']) ? $record['photo'][0] : $record['photo']; 
     41        if (!preg_match('![^a-z0-9/=+-]!i', $data)) 
     42            $data = base64_decode($data, true); 
     43    } 
     44     
     45    header('Content-Type: ' . rc_image_content_type($data)); 
     46    echo $data ? $data : file_get_contents('program/blank.gif'); 
     47    exit; 
     48} 
    2849 
    29 function rcmail_contact_details($attrib) 
     50 
     51function rcmail_contact_head($attrib) 
    3052{ 
    3153    global $CONTACTS, $RCMAIL; 
     
    3759    } 
    3860 
     61    $microformats = array('name' => 'fn', 'email' => 'email'); 
     62 
     63    $form = array( 
     64        'head' => array(  // section 'head' is magic! 
     65            'content' => array( 
     66                'prefix' => array('type' => 'text'), 
     67                'firstname' => array('type' => 'text'), 
     68                'middlename' => array('type' => 'text'), 
     69                'surname' => array('type' => 'text'), 
     70                'suffix' => array('type' => 'text'), 
     71            ), 
     72        ), 
     73    ); 
     74 
     75    unset($attrib['name']); 
     76    return rcmail_contact_form($form, $record, $attrib); 
     77} 
     78 
     79 
     80function rcmail_contact_details($attrib) 
     81{ 
     82    global $CONTACTS, $RCMAIL, $CONTACT_COLTYPES; 
     83 
     84    // check if we have a valid result 
     85    if (!(($result = $CONTACTS->get_result()) && ($record = $result->first()))) { 
     86        //$RCMAIL->output->show_message('contactnotfound'); 
     87        return false; 
     88    } 
     89 
    3990    $i_size = !empty($attrib['size']) ? $attrib['size'] : 40; 
    40     $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 6; 
    41     $t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40; 
    42  
    43     $microformats = array('name' => 'fn', 'email' => 'email'); 
    4491 
    4592    $form = array( 
     
    4794            'name'    => rcube_label('contactproperties'), 
    4895            'content' => array( 
    49                 'name' => array('type' => 'text', 'size' => $i_size), 
    50                 'firstname' => array('type' => 'text', 'size' => $i_size), 
    51                 'surname' => array('type' => 'text', 'size' => $i_size), 
    52                 'email' => array('type' => 'text', 'size' => $i_size), 
     96              'gender' => array('size' => $i_size), 
     97              'maidenname' => array('size' => $i_size), 
     98              'email' => array('size' => $i_size, 'render_func' => 'rcmail_render_email_value'), 
     99              'phone' => array('size' => $i_size), 
     100              'address' => array(), 
     101              'birthday' => array('size' => $i_size), 
     102              'anniversary' => array('size' => $i_size), 
     103              'website' => array('size' => $i_size, 'render_func' => 'rcmail_render_url_value'), 
     104              'im' => array('size' => $i_size), 
     105              'manager' => array('size' => $i_size), 
     106              'assistant' => array('size' => $i_size), 
     107              'spouse' => array('size' => $i_size), 
    53108            ), 
    54109        ), 
    55         'groups' => array( 
     110    ); 
     111     
     112    if (isset($CONTACT_COLTYPES['notes'])) { 
     113        $form['notes'] = array( 
     114            'name'    => rcube_label('notes'), 
     115            'content' => array( 
     116                'notes' => array('type' => 'textarea', 'label' => false), 
     117            ), 
     118        ); 
     119    } 
     120     
     121    if ($CONTACTS->groups) { 
     122        $form['groups'] = array( 
    56123            'name'    => rcube_label('groups'), 
    57             'content' => '', 
    58         ), 
    59     ); 
    60  
    61     // Get content of groups fieldset 
    62     if ($groups = rcmail_contact_record_groups($record['ID'])) { 
    63         $form['groups']['content'] = $groups;     
    64     } 
    65     else { 
    66         unset($form['groups']); 
    67     } 
    68  
    69     if (!empty($record['email'])) { 
    70         $form['info']['content']['email']['value'] = html::a(array( 
    71             'href' => 'mailto:' . $record['email'], 
    72             'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($record['email'])), 
    73             'title' => rcube_label('composeto'), 
    74             'class' => $microformats['email'], 
    75         ), Q($record['email'])); 
    76     } 
    77     foreach (array('name', 'firstname', 'surname') as $col) { 
    78         if ($record[$col]) { 
    79             $form['info']['content'][$col]['value'] = html::span($microformats[$col], Q($record[$col])); 
    80         } 
     124            'content' => rcmail_contact_record_groups($record['ID']), 
     125        ); 
    81126    } 
    82127 
    83128    return rcmail_contact_form($form, $record); 
     129} 
     130 
     131 
     132function rcmail_render_email_value($email, $col) 
     133{ 
     134    return html::a(array( 
     135        'href' => 'mailto:' . $email, 
     136        'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($email)), 
     137        'title' => rcube_label('composeto'), 
     138        'class' => 'email', 
     139    ), Q($email)); 
     140} 
     141 
     142 
     143function rcmail_render_url_value($url, $col) 
     144{ 
     145    $prefix = preg_match('![htfps]+://!', $url) ? '' : 'http://'; 
     146    return html::a(array( 
     147        'href' => $prefix . $url, 
     148        'target' => '_blank', 
     149        'class' => 'url', 
     150    ), Q($url)); 
    84151} 
    85152 
     
    125192 
    126193//$OUTPUT->framed = $_framed; 
     194$OUTPUT->add_handler('contacthead', 'rcmail_contact_head'); 
    127195$OUTPUT->add_handler('contactdetails', 'rcmail_contact_details'); 
     196$OUTPUT->add_handler('contactphoto', 'rcmail_contact_photo'); 
    128197 
    129198$OUTPUT->send('contact'); 
  • trunk/roundcubemail/program/steps/mail/autocomplete.inc

    r3989 r4424  
    3030    $abook->set_pagesize(1000);  // TODO: limit number of group members by config 
    3131    $result = $abook->list_records(array('email','name')); 
    32     while ($result && ($sql_arr = $result->iterate())) 
    33       $members[] = format_email_recipient($sql_arr['email'], $sql_arr['name']); 
     32    while ($result && ($sql_arr = $result->iterate())) { 
     33      foreach ((array)$sql_arr['email'] as $email) 
     34        $members[] = format_email_recipient($email, $sql_arr['name']); 
     35    } 
    3436 
    3537    $OUTPUT->command('replace_group_recipients', $gid, join(', ', $members)); 
     
    4648    if ($result = $abook->search(array('email','name'), $search, false, true, true, 'email')) { 
    4749      while ($sql_arr = $result->iterate()) { 
    48         $contact = format_email_recipient($sql_arr['email'], $sql_arr['name']); 
    49         // when we've got more than one book, we need to skip duplicates 
    50         if ($books_num == 1 || !in_array($contact, $contacts)) { 
    51           $contacts[] = $contact; 
    52           if (count($contacts) >= $MAXNUM) 
    53             break 2; 
     50        foreach ((array)$abook->get_col_values('email', $sql_arr, true) as $email) { 
     51          $contact = format_email_recipient($email, $sql_arr['name']); 
     52          // when we've got more than one book, we need to skip duplicates 
     53          if ($books_num == 1 || !in_array($contact, $contacts)) { 
     54            $contacts[] = $contact; 
     55            if (count($contacts) >= $MAXNUM) 
     56              break 2; 
     57          } 
    5458        } 
    5559      } 
  • trunk/roundcubemail/program/steps/mail/compose.inc

    r4410 r4424  
    11341134  $out = html::div($attrib, 
    11351135    $OUTPUT->form_tag(array('name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'), 
    1136       html::div(null, rcmail_compose_attachment_field(array('size' => $attrib[attachmentfieldsize]))) . 
     1136      html::div(null, rcmail_compose_attachment_field(array('size' => $attrib['attachmentfieldsize']))) . 
    11371137      html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) . 
    11381138      html::div('buttons', 
  • trunk/roundcubemail/skins/default/addressbook.css

    r3989 r4424  
    215215} 
    216216 
     217#contacttabs 
     218{ 
     219        position: relative; 
     220        padding-bottom: 22px; 
     221} 
     222 
     223#contacttabs div.tabsbar { 
     224        top: 0; 
     225        left: 2px; 
     226} 
     227 
     228#contacttabs fieldset.tabbed { 
     229        position: relative; 
     230        top: 22px; 
     231        min-height: 5em; 
     232} 
     233 
     234#contacthead 
     235{ 
     236        margin-bottom: 1em; 
     237        border: 0; 
     238        padding: 0; 
     239} 
     240 
     241#contacthead .names span.namefield, 
     242#contacthead .names input 
     243{ 
     244        font-size: 140%; 
     245} 
     246 
     247#contacthead .displayname span.namefield 
     248{ 
     249        font-size: 120%; 
     250} 
     251 
     252#contacthead span.nickname:before, 
     253#contacthead span.nickname:after, 
     254#contacthead input.ff_nickname:before, 
     255#contacthead input.ff_nickname:after 
     256{ 
     257        content: '"'; 
     258} 
     259 
     260#contacthead input 
     261{ 
     262        margin-right: 6px; 
     263        margin-bottom: 0.2em; 
     264} 
     265 
     266#contacthead .names input, 
     267#contacthead .addnames input, 
     268#contacthead .jobnames input 
     269{ 
     270        width: 180px; 
     271} 
     272 
     273#contacthead input.ff_prefix, 
     274#contacthead input.ff_suffix 
     275{ 
     276        width: 90px; 
     277} 
     278 
     279#contacthead .addnames input.ff_name 
     280{ 
     281        width: 374px; 
     282} 
     283 
     284#contactphoto 
     285{ 
     286        float: right; 
     287        width: 60px; 
     288        margin-left: 3em; 
     289        margin-right: 4px; 
     290} 
     291 
     292#contactpic 
     293{ 
     294        width: 60px; 
     295        min-height: 60px; 
     296        border: 1px solid #ccc; 
     297        background: white; 
     298} 
     299 
     300#contactpic img { 
     301        width: 60px; 
     302} 
     303 
     304#contactphoto .formlinks 
     305{ 
     306        margin-top: 0.5em; 
     307        text-align: center; 
     308} 
     309 
     310fieldset.contactfieldgroup 
     311{ 
     312        border: 0; 
     313        margin: 0.5em 0; 
     314        padding: 0.5em 2px; 
     315} 
     316 
     317fieldset.contactfieldgroup legend 
     318{ 
     319        font-size: 0.9em; 
     320} 
     321 
     322.contactfieldgroup .row 
     323{ 
     324        position: relative; 
     325        margin-bottom: 0.4em; 
     326} 
     327 
     328.contactfieldgroup .contactfieldlabel 
     329{ 
     330        position: absolute; 
     331        top: 0; 
     332        left: 2px; 
     333        width: 90px; 
     334        white-space: nowrap; 
     335        overflow: hidden; 
     336        text-overflow: ellipsis; 
     337        color: #666; 
     338        font-weight: bold; 
     339} 
     340 
     341.contactfieldgroup .contactfieldlabel select 
     342{ 
     343        width: 78px; 
     344        background: none; 
     345        border: 0; 
     346        color: #666; 
     347        font-weight: bold; 
     348        padding-left: 0; 
     349} 
     350 
     351.contactfieldgroup .contactfieldcontent 
     352{ 
     353        padding-left: 100px; 
     354        min-height: 1em; 
     355        line-height: 1.3em; 
     356} 
     357 
     358.contactfieldgroup .contactfield { 
     359        line-height: 1.3em; 
     360} 
     361 
     362.contactcontrolleraddress .contactfieldcontent input { 
     363        margin-bottom: 0.1em; 
     364} 
     365 
     366.contactfieldcontent .contactfieldbutton { 
     367        vertical-align: middle; 
     368        margin-left: 0.5em; 
     369} 
     370 
     371#upload-form 
     372{ 
     373        padding: 6px; 
     374} 
     375 
     376#upload-form div 
     377{ 
     378        padding: 2px; 
     379} 
  • trunk/roundcubemail/skins/default/common.css

    r4398 r4424  
    7575  color: black; 
    7676  padding: 1px 3px; 
     77} 
     78 
     79input.placeholder, 
     80textarea.placeholder 
     81{ 
     82  color: #aaa; 
    7783} 
    7884 
     
    113119  color: #666; 
    114120  font-size: 11px; 
     121} 
     122 
     123.formlinks a, 
     124.formlinks a:visited 
     125{ 
     126  color: #CC0000; 
     127  font-size: 11px; 
     128  text-decoration: none; 
     129} 
     130 
     131.formlinks a.disabled, 
     132.formlinks a.disabled:visited 
     133{ 
     134  color: #999999; 
    115135} 
    116136 
  • trunk/roundcubemail/skins/default/functions.js

    r4385 r4424  
    2626function rcube_init_tabs(id, current) 
    2727{ 
    28   var content = document.getElementById(id), 
    29     // get fieldsets of the higher-level (skip nested fieldsets) 
    30     fs = $('fieldset', content).not('fieldset > fieldset'); 
     28  var content = $('#'+id), 
     29    fs = content.children('fieldset'); 
    3130 
    3231  if (!fs.length) 
     
    4342  // convert fildsets into tabs 
    4443  fs.each(function(idx) { 
    45     var tab, a, elm = $(this), 
    46       // get first legend element 
    47       legend = $(elm).children('legend'); 
     44    var tab, a, elm = $(this), legend = elm.children('legend'); 
    4845 
    4946    // create a tab 
     
    6764function rcube_show_tab(id, index) 
    6865{ 
    69   var content = document.getElementById(id), 
    70     fs = $('fieldset', content).not('fieldset > fieldset'); 
     66  var fs = $('#'+id).children('fieldset'); 
    7167 
    7268  fs.each(function(idx) { 
     
    9591    composemenu:    {id:'composeoptionsmenu', editable:1}, 
    9692    // toggle: #1486823, #1486930 
    97     uploadmenu:     {id:'attachment-form', editable:1, above:1, toggle:!bw.ie&&!bw.linux } 
     93    uploadmenu:     {id:'attachment-form', editable:1, above:1, toggle:!bw.ie&&!bw.linux }, 
     94    uploadform:     {id:'upload-form', editable:1, toggle:!bw.ie&&!bw.linux } 
    9895  }; 
    9996 
     
    136133    if (!above && pos.top + ref.offsetHeight + obj.height() > window.innerHeight) 
    137134      above = true; 
     135 
     136    if (pos.left + obj.width() > window.innerWidth) 
     137      pos.left = window.innerWidth - obj.width() - 30; 
    138138 
    139139    obj.css({ left:pos.left, top:(pos.top + (above ? -obj.height() : ref.offsetHeight)) }); 
     
    501501      rcmail_ui.init_compose_form(); 
    502502  } 
     503  else if (rcmail.env.task == 'addressbook') { 
     504    rcmail.addEventListener('afterupload-photo', function(){ rcmail_ui.show_popup('uploadform', false); }); 
     505  } 
    503506} 
    504507 
  • trunk/roundcubemail/skins/default/iehacks.css

    r4304 r4424  
    237237  margin-top: 2px; 
    238238} 
     239 
     240.contactfieldgroup legend 
     241{ 
     242        padding: 0 0 0.5em 0; 
     243        margin-left: -4px; 
     244} 
  • trunk/roundcubemail/skins/default/mail.css

    r4328 r4424  
    13431343} 
    13441344 
    1345 .formlinks a, 
    1346 .formlinks a:visited 
    1347 { 
    1348   color: #999999; 
    1349   font-size: 11px; 
    1350   text-decoration: none; 
    1351 } 
    1352  
    1353 .formlinks a, 
    1354 .formlinks a:visited 
    1355 { 
    1356   color: #CC0000; 
    1357 } 
    1358  
    13591345#compose-editorfooter 
    13601346{ 
  • trunk/roundcubemail/skins/default/templates/contact.html

    r4176 r4424  
    1010<div id="contact-title" class="boxtitle"><roundcube:label name="contactproperties" /></div> 
    1111<div id="contact-details" class="boxcontent"> 
    12   <roundcube:object name="contactdetails" /> 
     12  <div id="contactphoto"><roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" /></div> 
     13  <roundcube:object name="contacthead" id="contacthead" /> 
     14  <div style="clear:both"></div> 
     15  <div id="contacttabs"> 
     16    <roundcube:object name="contactdetails" /> 
     17  </div> 
    1318  <p> 
    1419    <roundcube:button command="edit" type="input" class="button" label="editcontact" condition="!ENV:readonly" /> 
    1520  </p> 
    1621</div> 
    17 <script type="text/javascript">rcube_init_tabs('contact-details')</script> 
     22<script type="text/javascript">rcube_init_tabs('contacttabs')</script> 
    1823 
    1924</body> 
  • trunk/roundcubemail/skins/default/templates/contactadd.html

    r4176 r4424  
    66<script type="text/javascript" src="/functions.js"></script> 
    77</head> 
    8 <body class="iframe"> 
     8<body class="iframe" onload="rcube_init_mail_ui()"> 
    99 
    1010<div id="contact-title" class="boxtitle"><roundcube:label name="addcontact" /></div> 
    1111<div id="contact-details" class="boxcontent"> 
    12   <roundcube:object name="contacteditform" size="40" /> 
     12<form name="editform" method="post" action="./"> 
     13  <div id="contactphoto"> 
     14    <roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" /> 
     15    <div class="formlinks"> 
     16      <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/> 
     17      <roundcube:button command="delete-photo" type="link" label="delete" class="disabled" classAct="active" condition="env:photocol" /> 
     18    </div> 
     19  </div> 
     20  <roundcube:object name="contactedithead" id="contacthead" size="16" form="editform" /> 
     21  <div style="clear:both"></div> 
     22   
     23  <div id="contacttabs"> 
     24    <roundcube:object name="contacteditform" size="40" textareacols="60" deleteIcon="/images/icons/delete.png" form="editform" /> 
     25  </div> 
    1326  <p> 
    1427    <input type="button" value="<roundcube:label name="cancel" />" class="button" onclick="history.back()" />&nbsp; 
     
    1730</form> 
    1831</div> 
    19 <script type="text/javascript">rcube_init_tabs('contact-details')</script> 
     32 
     33<roundcube:object name="photoUploadForm" id="upload-form" size="30" class="popupmenu" /> 
     34 
     35<script type="text/javascript">rcube_init_tabs('contacttabs')</script> 
    2036 
    2137</body> 
  • trunk/roundcubemail/skins/default/templates/contactedit.html

    r4176 r4424  
    66<script type="text/javascript" src="/functions.js"></script> 
    77</head> 
    8 <body class="iframe"> 
     8<body class="iframe" onload="rcube_init_mail_ui()"> 
    99 
    1010<div id="contact-title" class="boxtitle"><roundcube:label name="editcontact" /></div> 
    1111<div id="contact-details" class="boxcontent"> 
    12   <roundcube:object name="contacteditform" size="40" /> 
     12<form name="editform" method="post" action="./"> 
     13  <div id="contactphoto"> 
     14    <roundcube:object name="contactphoto" id="contactpic" placeholder="/images/contactpic.png" /> 
     15    <div class="formlinks"> 
     16      <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/> 
     17      <roundcube:button command="delete-photo" type="link" label="delete" class="disabled" classAct="active" condition="env:photocol" /> 
     18    </div> 
     19  </div> 
     20  <roundcube:object name="contactedithead" id="contacthead" size="16" form="editform" /> 
     21  <div style="clear:both"></div> 
     22   
     23  <div id="contacttabs"> 
     24    <roundcube:object name="contacteditform" size="40" textareacols="60" deleteIcon="/images/icons/delete.png" form="editform" /> 
     25  </div> 
    1326  <p> 
    1427    <roundcube:button command="show" type="input" class="button" label="cancel" />&nbsp; 
     
    1730</form> 
    1831</div> 
    19 <script type="text/javascript">rcube_init_tabs('contact-details')</script> 
     32 
     33<roundcube:object name="photoUploadForm" id="upload-form" size="30" class="popupmenu" /> 
     34 
     35<script type="text/javascript">rcube_init_tabs('contacttabs')</script> 
    2036 
    2137</body> 
Note: See TracChangeset for help on using the changeset viewer.