Changeset 4230 in subversion


Ignore:
Timestamp:
Nov 17, 2010 7:50:33 AM (3 years ago)
Author:
thomasb
Message:

Save and read rich contact data using vcard format

Location:
branches/devel-addressbook
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • branches/devel-addressbook/program/include/rcube_contacts.php

    r4227 r4230  
    346346 
    347347        if ($sql_arr = $this->db->fetch_assoc()) { 
    348             $sql_arr['ID'] = $sql_arr[$this->primary_key]; 
     348            $record = $this->convert_db_data($sql_arr); 
    349349            $this->result = new rcube_result_set(1); 
    350             $this->result->add($sql_arr); 
    351         } 
    352  
    353         return $assoc && $sql_arr ? $sql_arr : $this->result; 
     350            $this->result->add($record); 
     351        } 
     352 
     353        return $assoc && $record ? $record : $this->result; 
    354354    } 
    355355 
     
    451451        $updated = false; 
    452452        $write_sql = array(); 
    453         $save_cols = $this->convert_save_data($save_cols); 
     453        $record = $this->get_record($id, true); 
     454        $save_cols = $this->convert_save_data($save_cols, $record); 
    454455 
    455456        foreach ($save_cols as $col => $value) { 
     
    473474        return $updated; 
    474475    } 
    475  
    476  
    477     private function convert_save_data($save_data) 
     476     
     477     
     478    private function convert_db_data($sql_arr) 
     479    { 
     480        $record = array(); 
     481        $record['ID'] = $sql_arr[$this->primary_key]; 
     482         
     483        if ($sql_arr['vcard']) { 
     484          $vcard = new rcube_vcard($sql_arr['vcard']); 
     485          $record += $vcard->get_assoc(); 
     486        } 
     487        else 
     488          $record += $sql_arr; 
     489         
     490        return $record; 
     491    } 
     492 
     493 
     494    private function convert_save_data($save_data, $record = array()) 
    478495    { 
    479496        $out = array(); 
    480          
     497 
     498        // copy values into vcard object 
     499        $vcard = new rcube_vcard($record['vcard']); 
     500        foreach ($save_data as $key => $values) { 
     501            list($field, $section) = explode(':', $key); 
     502            foreach ((array)$values as $value) { 
     503                if (strlen($value) || is_array($value)) 
     504                    $vcard->set($field, $value, strtoupper($section)); 
     505            } 
     506        } 
     507        $out['vcard'] = $vcard->export(); 
     508 
    481509        foreach ($this->table_cols as $col) { 
    482510            $key = $col; 
     
    486514                $out[$col] = is_array($save_data[$key]) ? join(',', $save_data[$key]) : $save_data[$key]; 
    487515        } 
    488          
    489         // TODO: save all data into a vcard 
    490          
     516 
    491517        return $out; 
    492518    } 
  • branches/devel-addressbook/program/include/rcube_vcard.php

    r4028 r4230  
    3333    'N' => array(array('','','','','')), 
    3434  ); 
     35  private $fieldmap = array('phone' => 'TEL', 'birthday' => 'BDAY', 'website' => 'URL', 'notes' => 'NOTE', 'email' => 'EMAIL', 'address' => 'ADR'); 
    3536 
    3637  public $business = false; 
     
    9899 
    99100  /** 
     101   * Return vCard data as associative array to be unsed in Roundcube address books 
     102   * 
     103   * @return array Hash array with key-value pairs 
     104   */ 
     105  public function get_assoc() 
     106  { 
     107    $out = array('name' => $this->displayname); 
     108     
     109    foreach (array('firstname','surname','middlename','nickname','organization') as $col) 
     110      $out[$col] = $this->$col; 
     111     
     112    foreach (array_flip($this->fieldmap) as $tag => $col) { 
     113      foreach ((array)$this->raw[$tag] as $i => $raw) { 
     114        if (is_array($raw)) { 
     115          $k = 0; 
     116          $key = $col; 
     117          $subtype = strtolower($raw['type'][$k++]); 
     118          while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref')) 
     119            $subtype = strtolower($raw['type'][$k++]); 
     120          if ($subtype) 
     121            $key .= ':' . $subtype; 
     122             
     123          if ($tag == 'ADR') { 
     124            list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw; 
     125            $out[$key][] = $value; 
     126          } 
     127          else 
     128            $out[$key][] = $raw[0]; 
     129        } 
     130        else { 
     131          $out[$col][] = $raw; 
     132        } 
     133      } 
     134    } 
     135     
     136    return $out; 
     137  } 
     138 
     139 
     140  /** 
    100141   * Convert the data structure into a vcard 3.0 string 
    101142   */ 
     
    115156  public function set($field, $value, $section = 'HOME') 
    116157  { 
     158    static $touched = array(); 
     159     
    117160    switch ($field) { 
    118161      case 'name': 
     
    138181         
    139182      case 'email': 
    140         $index = $this->get_type_index('EMAIL', $section); 
    141         if (!is_array($this->raw['EMAIL'][$index])) { 
    142           $this->raw['EMAIL'][$index] = array(0 => $value, 'type' => array('INTERNET', $section, 'pref')); 
    143         } 
    144         else { 
    145           $this->raw['EMAIL'][$index][0] = $value; 
     183        if (!$touched['EMAIL']++) 
     184          $this->raw['EMAIL'] = array(); 
     185        $index = count($this->raw['EMAIL']); 
     186        $this->raw['EMAIL'][$index] = array(0 => $value, 'type' => array_filter(array('INTERNET', $section))); 
     187        break; 
     188 
     189      case 'address': 
     190        $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']); 
     191        // fall through 
     192 
     193      default: 
     194        if ($tag = $this->fieldmap[$field]) { 
     195          if (!$touched[$tag]++) 
     196            $this->raw[$tag] = array(); 
     197          $index = count($this->raw[$tag]); 
     198          $this->raw[$tag][$index] = (array)$value; 
     199          if ($section) 
     200            $this->raw[$tag][$index]['type'] = array($section); 
    146201        } 
    147202        break; 
  • branches/devel-addressbook/program/js/app.js

    r4227 r4230  
    325325            this.init_edit_field(col, null); 
    326326 
    327           $('#addfieldmenu').change(function(e){ 
    328             ref.insert_edit_field($(this).val(), $(this).attr('rel')); 
     327          $('select.addfieldmenu').change(function(e){ 
     328            ref.insert_edit_field($(this).val(), $(this).attr('rel'), this); 
    329329            this.selectedIndex = 0; 
    330330          }); 
     
    39903990  }; 
    39913991 
    3992   this.insert_edit_field = function(col, section) 
     3992  this.insert_edit_field = function(col, section, menu) 
    39933993  { 
    39943994    // just make pre-defined input field visible 
     
    39963996    if (elem.length) { 
    39973997      elem.show().focus(); 
     3998      $(menu).children('option[value="'+col+'"]').attr('disabled', true); 
    39983999    } 
    39994000    else { 
     
    40444045          row.append(label).append(cell).appendTo(appendcontainer); 
    40454046          input.first().focus(); 
     4047           
     4048          // disable option if limit reached 
     4049          if (!colprop.count) colprop.count = 0; 
     4050          if (colprop.limit && ++colprop.count == colprop.limit) 
     4051            $(menu).children('option[value="'+col+'"]').attr('disabled', true); 
    40464052        } 
    40474053      } 
  • branches/devel-addressbook/program/localization/en_US/labels.inc

    r4184 r4230  
    276276$labels['newcontactgroup'] = 'Create new contact group'; 
    277277$labels['groupactions']   = 'Actions for contact groups...'; 
     278 
     279$labels['addfield'] = 'Add field...'; 
     280$labels['notes'] = 'Notes'; 
    278281 
    279282$labels['previouspage']   = 'Show previous set'; 
  • branches/devel-addressbook/program/steps/addressbook/edit.inc

    r4227 r4230  
    9797                'phone' => array('size' => $i_size, 'visible' => true), 
    9898                'address' => array('visible' => true), 
     99                'birthday' => array('size' => $i_size), 
     100                'website' => array('size' => $i_size), 
     101                'im' => array('size' => $i_size), 
    99102            ), 
    100103        ), 
     
    102105            'name'    => rcube_label('notes'), 
    103106            'content' => array( 
    104                 'notes' => array('type' => 'textarea', 'size' => $t_cols, 'rows' => $t_rows, 'label' => false, 'visible' => true), 
     107                'notes' => array('size' => $t_cols, 'rows' => $t_rows, 'label' => false, 'visible' => true, 'limit' => 1), 
    105108            ), 
    106109            'single' => true, 
  • branches/devel-addressbook/program/steps/addressbook/func.inc

    r4227 r4230  
    5757 
    5858 
    59 // default coltypes 
     59// TODO: let the $CONTACT object define the list of possible coltypes 
    6060$CONTACT_COLTYPES = array( 
    61   'name'         => array('type' => 'text', 'size' => 40, 'label' => rcube_label('name')), 
    62   'firstname'    => array('type' => 'text', 'size' => 19, 'label' => rcube_label('firstname')), 
    63   'surname'      => array('type' => 'text', 'size' => 19, 'label' => rcube_label('surname')), 
    64   'middlename'   => array('type' => 'text', 'size' => 19, 'label' => rcube_label('middlename')), 
    65   'nickname'     => array('type' => 'text', 'size' => 40, 'label' => rcube_label('nickname')), 
    66   'jobtitle'     => array('type' => 'text', 'size' => 40, 'label' => rcube_label('jobtitle')), 
    67   'organization' => array('type' => 'text', 'size' => 19, 'label' => rcube_label('organization')), 
    68   'department'   => array('type' => 'text', 'size' => 19, 'label' => rcube_label('department')), 
     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  'nickname'     => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('nickname')), 
     66  'jobtitle'     => array('type' => 'text', 'size' => 40, 'limit' => 1, 'label' => rcube_label('jobtitle')), 
     67  'organization' => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('organization')), 
     68  'department'   => array('type' => 'text', 'size' => 19, 'limit' => 1, 'label' => rcube_label('department')), 
    6969  'email'        => array('type' => 'text', 'size' => 40, 'label' => rcube_label('email'), 'subtypes' => array('home','work','other')), 
    7070  'phone'        => array('type' => 'text', 'size' => 40, 'label' => rcube_label('phone'), 'subtypes' => array('home','home2','work','work2','mobile','main','homefax','workfax','car','pager','assistant','other')), 
     
    7575    'region'     => array('type' => 'text', 'size' => 12, 'label' => rcube_label('region'), 'composite' => true), 
    7676    'country'    => array('type' => 'text', 'size' => 40, 'label' => rcube_label('country'), 'composite' => true), 
    77   'birthday'     => array('type' => 'date', 'size' => 12, 'label' => rcube_label('birthday')), 
     77  'birthday'     => array('type' => 'date', 'size' => 12, 'label' => rcube_label('birthday'), 'limit' => 1), 
    7878  'website'      => array('type' => 'text', 'size' => 40, 'label' => rcube_label('website'), 'subtypes' => array('home','work','blog','other')), 
    7979  'im'           => array('type' => 'text', 'size' => 40, 'label' => rcube_label('instantmessenger'), 'subtypes' => array('aim','icq','msn','yahoo','jabber','other')), 
     80  'notes'        => array('type' => 'textarea', 'size' => 40, 'rows' => 15, 'label' => rcube_label('notes'), 'limit' => 1), 
    8081); 
    8182 
     
    275276    $coltypes = $GLOBALS['CONTACT_COLTYPES']; 
    276277     
    277     $select_add = new html_select(array('id' => 'addfieldmenu')); 
    278     $select_add->add(rcube_label('addfield'), ''); 
    279     $select_add->add($coltypes['middlename']['label'], 'middlename'); 
    280278    foreach ($coltypes as $col => $prop) { 
    281         if (!$prop['composite'] && !$prop['default']) 
    282             $select_add->add($prop['label'], $col); 
    283279        if ($prop['subtypes']) { 
    284280            $select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype')); 
     
    292288        if (empty($fieldset['content'])) 
    293289            continue; 
     290 
     291        $select_add = new html_select(array('class' => 'addfieldmenu', 'rel' => $section)); 
     292        $select_add->add(rcube_label('addfield'), ''); 
    294293 
    295294        // render head section with name fields (not a regular list of rows) 
     
    317316                        $colprop = (array)$fieldset['content'][$col] + (array)$coltypes[$col]; 
    318317                        $colprop['id'] = 'ff_'.$col; 
    319                         if (empty($record[$col]) && !$colprop['visible']) 
     318                        if (empty($record[$col]) && !$colprop['visible']) { 
    320319                            $colprop['style'] = 'display:none'; 
     320                            $select_add->add($colprop['label'], $col); 
     321                        } 
    321322                        $fields .= rcmail_get_edit_field($col, $record[$col], $colprop, $colprop['type']); 
    322323                    } 
     
    324325                $content .= html::div($blockname, $fields); 
    325326            } 
     327             
     328            if ($RCMAIL->action != 'show') 
     329                $content .= html::p('addfield', $select_add->show(null)); 
    326330 
    327331            $out .= html::tag('fieldset', $attrib, (!empty($fieldset['name']) ? html::tag('legend', null, Q($fieldset['name'])) : '') . $content) ."\n"; 
     
    338342                  $subtype = 'home'; 
    339343 
    340                 $fullkey = $col.'.'.$subtype; 
     344                $fullkey = $col.':'.$subtype; 
    341345                $label = isset($colprop['label']) ? $colprop['label'] : rcube_label($col); 
    342346                 
     
    359363                        foreach ($colprop['subtypes'] as $i => $st) { 
    360364                            $newval = false; 
    361                             if ($record[$field.'.'.$st]) { 
     365                            if ($record[$field.':'.$st]) { 
    362366                                $subtypes[count($values)] = $st; 
    363                                 $newval = $record[$field.'.'.$st]; 
     367                                $newval = $record[$field.':'.$st]; 
    364368                            } 
    365369                            else if ($i == 0 && $record[$field]) { 
     
    387391                foreach ((array)$values as $i => $val) { 
    388392                    if ($subtypes[$i]) 
    389                         $subtype = $subtypes; 
     393                        $subtype = $subtypes[$i]; 
    390394 
    391395                    // render composite field 
     
    393397                        $composite = ''; 
    394398                        foreach ($colprop['childs'] as $j => $childcol) { 
    395                             $childvalue = $val[$coldcol] ? $val[$coldcol] : $val[$j]; 
    396                              
     399                            $childvalue = $val[$childcol] ? $val[$childcol] : $val[$j]; 
     400 
    397401                            if ($RCMAIL->action != 'show') { 
    398402                                $cp = $coltypes[$childcol]; 
     
    401405                            } 
    402406                            else 
    403                                 $composite .= html::span('data', Q($childvalue)) . " "; 
     407                                $composite .= html::span('data ' . $childcol, Q($childvalue)) . " "; 
    404408                        } 
    405409 
     
    413417                            $colprop['array'] = true; 
    414418                        $val = rcmail_get_edit_field($col, $val, $colprop, $colprop['type']); 
    415                     } 
     419                        $coltypes[$field]['count']++; 
     420                    } 
     421                    else if (!$colprop['value']) 
     422                        $val = Q($val); 
    416423 
    417424                    if ($colprop['subtypes']) 
    418425                        $label = $subtype; 
    419                          
     426 
    420427                    if ($label) { 
    421428                        $rows .= html::div('row', 
     
    427434                } 
    428435                 
     436                // add option to the add-field menu 
     437                if (!$colprop['limit'] || $coltypes[$field]['count'] < $colprop['limit']) { 
     438                    $select_add->add($colprop['label'], $col); 
     439                    $select_add->_count++; 
     440                } 
     441                 
    429442                $content .= html::div('contactfieldgroup contactcontroller' . $col, $rows); 
    430443            } 
    431444 
    432             if ($RCMAIL->action != 'show' && !$fieldset['single']) 
    433                 $content .= html::p('addfield', $select_add->show(null, array('rel' => $section))); 
     445            if ($RCMAIL->action != 'show' && $select_add->_count) 
     446                $content .= html::p('addfield', $select_add->show(null)); 
    434447 
    435448            $content = html::div(array('id' => 'contactsection' . $section), $content); 
  • branches/devel-addressbook/program/steps/addressbook/save.inc

    r4227 r4230  
    3737  if ($colprop['composite']) 
    3838    continue; 
     39  // gather form data of composite fields 
    3940  if ($colprop['childs']) { 
    4041    $values = array(); 
     
    4950        $a_record[$col.':'.$subtype][] = $values[$i]; 
    5051  } 
     52  // assign values and subtypes 
    5153  else if (is_array($_POST[$fname])) { 
    5254    $values = get_input_value($fname, RCUBE_INPUT_POST); 
     
    6769#var_dump($a_record); 
    6870 
    69 // Basic input checks 
    70 if (empty($a_record['name']) || empty($a_record['email'])) { 
     71// Basic input checks (TODO: delegate to $CONTACTS instance) 
     72if (empty($a_record['name'])/* || empty($a_record['email'])*/) { 
    7173  $OUTPUT->show_message('formincomplete', 'warning'); 
    7274  rcmail_overwrite_action($return_action); 
     
    7577 
    7678// Validity checks 
    77 foreach ((array)$a_record['email'] as $email) { 
    78   $_email = idn_to_ascii($email); 
    79   if (!check_email($_email, false)) { 
    80     $OUTPUT->show_message('emailformaterror', 'warning', array('email' => $_email)); 
    81     rcmail_overwrite_action($return_action); 
    82     return; 
     79foreach ($a_record as $field => $values) { 
     80  if (strpos($field, 'email') === 0) { 
     81    foreach ((array)$values as $email) { 
     82      $_email = idn_to_ascii($email); 
     83      if (!check_email($_email, false)) { 
     84        $OUTPUT->show_message('emailformaterror', 'warning', array('email' => $email)); 
     85        rcmail_overwrite_action($return_action); 
     86        return; 
     87      } 
     88    } 
    8389  } 
    8490} 
  • branches/devel-addressbook/program/steps/addressbook/show.inc

    r4227 r4230  
    6565 
    6666    $i_size = !empty($attrib['size']) ? $attrib['size'] : 40; 
    67     $t_rows = !empty($attrib['textarearows']) ? $attrib['textarearows'] : 6; 
    68     $t_cols = !empty($attrib['textareacols']) ? $attrib['textareacols'] : 40; 
    6967 
    7068    $form = array( 
     
    7270            'name'    => rcube_label('contactproperties'), 
    7371            'content' => array( 
    74                 'email' => array('type' => 'text', 'size' => $i_size, 'value' => array()), 
     72              'email' => array('size' => $i_size, 'value' => array()), 
     73              'phone' => array('size' => $i_size), 
     74              'address' => array(), 
     75              'birthday' => array('size' => $i_size), 
     76              'website' => array('size' => $i_size), 
     77              'im' => array('size' => $i_size), 
    7578            ), 
    7679        ), 
     
    8790    ); 
    8891     
    89     foreach ((array)$record['email'] as $email) { 
    90         $form['info']['content']['email']['value'][] = html::a(array( 
    91             'href' => 'mailto:' . $email, 
    92             'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($email)), 
    93             'title' => rcube_label('composeto'), 
    94             'class' => 'email', 
    95         ), Q($email)); 
     92    foreach ((array)$record as $field => $values) { 
     93        if (strpos($field, 'email') === 0) { 
     94            foreach ((array)$values as $email) { 
     95                $form['info']['content']['email']['value'][] = html::a(array( 
     96                    'href' => 'mailto:' . $email, 
     97                    'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($email)), 
     98                    'title' => rcube_label('composeto'), 
     99                    'class' => 'email', 
     100                ), Q($email)); 
     101            } 
     102        } 
    96103    } 
    97104 
  • branches/devel-addressbook/skins/default/addressbook.css

    r4227 r4230  
    267267.contactfieldgroup 
    268268{ 
    269         margin: 1.2em 0; 
     269        margin: 0.8em 0; 
     270} 
     271 
     272form .contactfieldgroup 
     273{ 
     274        margin: 1.4em 0; 
    270275} 
    271276 
     
    310315        margin-bottom: 0.1em; 
    311316} 
     317 
     318.contactcontrolleraddress span.street, 
     319.contactcontrolleraddress span.country { 
     320        display: block; 
     321} 
Note: See TracChangeset for help on using the changeset viewer.