]> git.sur5r.net Git - contagged/commitdiff
configurable name <-> atrribute mappings
authorAndreas Gohr <gohr@cosmocode.de>
Thu, 1 Mar 2007 13:36:57 +0000 (14:36 +0100)
committerAndreas Gohr <gohr@cosmocode.de>
Thu, 1 Mar 2007 13:36:57 +0000 (14:36 +0100)
This makes the whole application much more flexible. It introduces a new
config file called fields where you can specify which schemas and attribute
names Contagged should use.

This patch is probably not complete, yet and breaks things. It will be
followed up by fixing patches.

darcs-hash:20070301133657-6e07b-208768759179631ef8563b5a738b727f08b82548.gz

entry.php
fields.php [new file with mode: 0644]
functions.php
index.php
init.php
tags.php
template.php
templates/entry_edit.tpl

index 0b22eca1b21e9675a02ab1caf7d8d4607f288a5e..eaeedbadecbb37b0dded5c5ebb68420cbee9d951 100644 (file)
--- a/entry.php
+++ b/entry.php
   //save data if asked for
   if($_SESSION['ldapab']['username'] && !empty($_REQUEST['save']) && $_REQUEST['save']){
     // prepare special data
-    $_REQUEST['entry']['jpegPhoto'][]=_getUploadData();
+    $_REQUEST['entry']['photo']  = _getUploadData();
     $_REQUEST['entry']['marker'] = explode(',',$_REQUEST['entry']['markers']);
-    $_REQUEST['entry']['marker'] = array_map('trim',$_REQUEST['entry']['marker']);
-    $_REQUEST['entry']['marker'] = array_unique($_REQUEST['entry']['marker']);
-    $_REQUEST['entry']['marker'] = array_filter($_REQUEST['entry']['marker']);
-    sort($_REQUEST['entry']['marker']);
     unset($_REQUEST['entry']['markers']);
-    
-    $_REQUEST['entry']['mail'] = array_map('trim',$_REQUEST['entry']['mail']);
-    $_REQUEST['entry']['mail'] = array_unique($_REQUEST['entry']['mail']);
-    $_REQUEST['entry']['mail'] = array_filter($_REQUEST['entry']['mail']);
-    sort($_REQUEST['entry']['mail']);
-    
+
+    foreach(array_keys($_REQUEST['entry']) as $field){
+        if($FIELDS['*'.$field]){
+            // entry has to be handled as array -> clean it up (trim, unique, sort)
+            $_REQUEST['entry'][$field] = array_map('trim',$_REQUEST['entry'][$field]);
+            $_REQUEST['entry'][$field] = array_unique($_REQUEST['entry'][$field]);
+            $_REQUEST['entry'][$field] = array_filter($_REQUEST['entry'][$field]);
+            natcasesort($_REQUEST['entry'][$field]);
+        }
+    }
     $dn = _saveData();
   }
 
@@ -120,11 +120,8 @@ print '</pre>';*/
   function _saveData(){
     global $LDAP_CON;
     global $conf;
-    $entries = namedentries();
-    $entries['mail']='mail';  //special field mail isn't in entries so we add it here
-    if($conf['extended']){
-      $entries['marker']='marker'; //same for marker in extended schema
-    }
+    global $FIELDS;
+    global $OCLASSES;
 
     $entry = $_REQUEST['entry'];
     $dn    = $_REQUEST['dn'];
@@ -137,7 +134,7 @@ print '</pre>';*/
     }else{
       $newdn .= ', '.$conf['publicbook'];
     }
-    $entry['cn']          = $entry['givenname'].' '.$entry['name'];;
+    $entry['displayname'] = $entry['givenname'].' '.$entry['name'];;
     $entry = prepare_ldap_entry($entry);
 
 /*
@@ -153,19 +150,16 @@ print '</pre>';
       tpl_ldaperror();
       return $newdn;
     }else{
-      // in extended mode we have to make sure the right classes are set
-      if($conf['extended']){
-        ldap_store_objectclasses($dn,array('inetOrgPerson','contactPerson'));
-      }
-      // in openxchange mode we have to make sure the right classes are set
-      if ($conf['openxchange']){
-        ldap_store_objectclasses($dn,array('inetOrgPerson','OXUserObject'));
-      }
-      //modify entry (touches only our attributes)
-      foreach (array_keys($entries) as $key){
+      // update the objectClasses
+      ldap_store_objectclasses($dn,$OCLASSES);
+      unset($entry['objectclass']);
+
+      //modify entry attribute by attribute - this ensure we don't delete unknown stuff
+      foreach (array_values($FIELDS) as $key){
         if($key == 'dn'){
           continue;
         }elseif(empty($entry[$key])){
+          // field is empty -> handle deletion (except for photo unless deletion triggered)
           if (empty($_REQUEST['delphoto'])) { $_REQUEST['delphoto']=0; }
           if($key == 'jpegPhoto' && !$_REQUEST['delphoto']){
             continue;
@@ -213,4 +207,4 @@ print '</pre>';
     }
     return '';
   }
-?>
+
diff --git a/fields.php b/fields.php
new file mode 100644 (file)
index 0000000..a87efa0
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Configures fieldname - LDAP attribute associations
+ *
+ * If you use other attributes you may change the assignments here.
+ * Note the arrays need to remain flippable, eg. both sides have to
+ * be unique
+ *
+ * Fields starting with a * may contain multiple values (needs to be
+ * handled by the template as well)
+ */
+
+
+/**
+ * The object classes to store with contacts
+ */
+$OCLASSES[] = 'inetOrgPerson';
+
+/**
+ * The standard fields suported by OpenLDAP's default schemas
+ */
+$FIELDS = array(
+    'dn'           => 'dn',                          // don't touch!
+    'name'         => 'sn',
+    'displayname'  => 'cn',
+    'givenname'    => 'givenName',
+    'title'        => 'title',
+    'organization' => 'o',                           // aka. company
+    'office'       => 'physicalDeliveryOfficeName',
+    'street'       => 'postalAddress',
+    'zip'          => 'postalCode',
+    'location'     => 'l',                           // aka. city
+    'phone'        => 'telephoneNumber',
+    'fax'          => 'facsimileTelephoneNumber',
+    'mobile'       => 'mobile',                      // aka. cell phone
+    'pager'        => 'pager',
+    'homephone'    => 'homePhone',
+    'homestreet'   => 'homePostalAddress',
+    'photo'        => 'jpegPhoto',
+    'url'          => 'labeledURI',
+    'note'         => 'description',
+    'manager'      => 'manager',                     // aka. key account
+    '*mail'        => 'mail',
+);
+
+/**
+ * If the provided "extended" schema is used the following fields
+ * and object classes are added
+ */
+$OCLASSES[] = 'contactPerson';
+$FIELDS['anniversary']  = 'anniversary';
+$FIELDS['*marker']      = 'marker';                  // aka. tags
+
+/**
+ * If the open exchange schema is used the following fields
+ * and object classes are added
+ */
+/* comment in if you want to use it
+$OCLASSES[] = 'OXUserObject';
+$FIELDS['country']          = 'userCountry';
+$FIELDS['birthday']         = 'birthDay';
+$FIELDS['ipphone']          = 'IPPhone';
+$FIELDS['*marker']          = 'OXUserCategories';
+$FIELDS['instantmessenger'] = 'OXUserInstantMessenger';
+$FIELDS['timezone']         = 'OXTimeZone';
+$FIELDS['position']         = 'OXUserPosition';
+$FIELDS['certificate']      = 'relClientCert';
+*/
+
+
+/**
+ * Flip the array
+ */
+$RFIELDS = array_flip($FIELDS);
+
index afa2e430faf490ca72ca5cfe8f2fa20cbd0a7f6c..c8de36794bee140bda2545bd99687486ef08cfe8 100644 (file)
@@ -1,4 +1,4 @@
-<?
+<?php
 
 /**
  * assigns some standard variables to smarty templates
@@ -196,54 +196,13 @@ function ldap_get_binentries($conn,$srchRslt){
   return $data;
 }
 
+
 /**
  * loads ldap names and their cleartext meanings from
  * entries.conf file and returns it as hash
  */
 function namedentries($flip=false){
-  global $conf;
-
-  $entries['dn']                         = 'dn';
-  $entries['sn']                         = 'name';
-  $entries['givenName']                  = 'givenname';
-  $entries['title']                      = 'title';
-  $entries['o']                          = 'organization';
-  $entries['physicalDeliveryOfficeName'] = 'office';
-  $entries['postalAddress']              = 'street';
-  $entries['postalCode']                 = 'zip';
-  $entries['l']                          = 'location';
-  $entries['telephoneNumber']            = 'phone';
-  $entries['facsimileTelephoneNumber']   = 'fax';
-  $entries['mobile']                     = 'mobile';
-  $entries['pager']                      = 'pager';
-  $entries['homePhone']                  = 'homephone';
-  $entries['homePostalAddress']          = 'homestreet';
-  $entries['jpegPhoto']                  = 'photo';
-  $entries['labeledURI']                 = 'url';
-  $entries['description']                = 'note';
-  $entries['manager']                    = 'manager';
-  $entries['cn']                         = 'displayname';
-
-  if($conf['extended']){
-    $entries['anniversary']              = 'anniversary';
-  }
-  if($conf['openxchange']){
-    $entries['mailDomain']               = 'domain';
-    $entries['userCountry']              = 'country';
-    $entries['birthDay']                 = 'birthday';
-    $entries['IPPhone']                  = 'ipphone';
-    $entries['OXUserCategories']         = 'categories';
-    $entries['OXUserInstantMessenger']   = 'instantmessenger';
-    $entries['OXTimeZone']               = 'timezone';
-    $entries['OXUserPosition']           = 'position';
-    $entries['relClientCert']            = 'certificate';
-  }
-
-  if($flip){
-    $entries = array_reverse($entries);
-    $entries = array_flip($entries);
-  }
-  return $entries;
+    trigger_error('deprecated namedentries called',E_USER_WARNING);
 }
 
 /**
@@ -251,34 +210,37 @@ function namedentries($flip=false){
  */
 function prepare_ldap_entry($in){
   global $conf;
-
-  //check dateformat
-  if(!preg_match('/\d\d\d\d-\d\d-\d\d/',$in['anniversary'])){
-    $in['anniversary']='';
-  }
-
-  $entries = namedentries(true);
-  foreach(array_keys($in) as $key){
-    if(empty($entries[$key])){
-      $keyname=$key;
-    }else{
-      $keyname=$entries[$key];
-    }
-    if(is_array($in[$key])){
-      $out[$keyname] = $in[$key];
+  global $FIELDS;
+  global $OCLASSES;
+
+  //check dateformats
+  if(!preg_match('/\d\d\d\d-\d\d-\d\d/',$in['anniversary'])) $in['anniversary']='';
+  if(!preg_match('/\d\d\d\d-\d\d-\d\d/',$in['birthday'])) $in['birthday']='';
+
+  // we map all internal names to the configured LDAP attributes here
+  foreach($in as $key => $value){
+    if($FIELDS[$key]){
+        // normal mapped field
+        $out[$FIELDS[$key]][] = $value;
+    }elseif($FIELDS["*$key"]){
+        // mapped multi field
+        if(is_array($value)){
+            $out[$FIELDS["*$key"]] = $value;
+        }else{
+            $out[$FIELDS["*$key"]][] = $value; //shouldn't happen, but to be sure
+        }
     }else{
-      $out[$keyname][] = $in[$key];
+        // no mapping found - assume it to be a LDAP attribute (shouldn't happen)
+        if(is_array($value)){
+            $out[$key] = $value;
+        }else{
+            $out[$key][] = $value;
+        }
     }
   }
 
-  //standard Objectclass
-  $out['objectclass'][] = 'inetOrgPerson';
-  if($conf['extended']){
-    $out['objectclass'][] = 'contactPerson';
-  }
-  if($conf['openxchange']){
-    $out['objectclass'][] = 'OXUserObject';
-  }
+  // add the Objectclasses
+  $out['objectclass'] = $OCLASSES;
 
   return clear_array($out);
 }
index ae8912952a83281c4d9eb2267938d8d61e8464f5..43ae1b0b47963bf2ec62d151d270dbb336eef8d9 100644 (file)
--- a/index.php
+++ b/index.php
@@ -17,7 +17,7 @@
   }else{
     $result2 = '';
   }
-  
+
   $result = array_merge((array)$result1,(array)$result2);
 
   // select entry template
    */
   function _namesort($a,$b){
     global $result;
-    if (empty($result[$a]['givenName'])) { $result[$a]['givenName']=''; }
-    if (empty($result[$b]['givenName'])) { $result[$b]['givenName']=''; }
-    $x = $result[$a]['sn'][0].$result[$a]['givenName'][0];
-    $y = $result[$b]['sn'][0].$result[$b]['givenName'][0];
+    global $FIELDS;
+    if (empty($result[$a][$FIELDS['givenname']])) { $result[$a][$FIELDS['givenname']]=''; }
+    if (empty($result[$b][$FIELDS['givenname']])) { $result[$b][$FIELDS['givenname']]=''; }
+    $x = $result[$a][$FIELDS['name']][0].$result[$a][$FIELDS['givenname']][0];
+    $y = $result[$b][$FIELDS['name']][0].$result[$b][$FIELDS['givenname']][0];
     return(strcasecmp($x,$y));
   }
 
@@ -93,8 +94,7 @@
    * Creates an LDAP filter from given request variables search or filter
    */
   function _makeldapfilter(){
-
-    $f_entries = namedentries(true);
+    global $FIELDS;
 
     //handle given filter
 
     if (empty($_REQUEST['search'])) { $_REQUEST['search']=''; }
     if (empty($_REQUEST['org'])) { $_REQUEST['org']=''; }
     if (empty($_REQUEST['marker'])) { $_REQUEST['marker']=''; }
-    if (empty($_REQUEST['categories'])) { $_REQUEST['categories']=''; }
     $filter = ldap_filterescape($_REQUEST['filter']);
     $search = ldap_filterescape($_REQUEST['search']);
     $org    = ldap_filterescape($_REQUEST['org']);
     $marker = ldap_filterescape($_REQUEST['marker']);
-    $categories = ldap_filterescape($_REQUEST['categories']);
     $_SESSION['ldapab']['filter'] = $_REQUEST['filter'];
     if(empty($filter)) $filter='a';
 
     if(!empty($marker)){
+      // Search by tag
       $ldapfilter = '(&(objectClass=contactPerson)';
       $marker = explode(',',$marker);
       foreach($marker as $m){
         $m = trim($m);
-        $ldapfilter .= "(marker=$m)";
+        $ldapfilter .= '('.$FIELDS['*marker'].'='.$m.')';
       }
       $ldapfilter .= ')';
-    }elseif(!empty($categories)){
-      $ldapfilter = "(&(objectClass=OXUserObject)(OXUserCategories=$categories))";
     }elseif(!empty($search)){
+      // Search name and organization
       $search = trim($search);
       $words=preg_split('/\s+/',$search);
       $filter='';
       foreach($words as $word){
-        $filter .= "(|(|(sn=*$word*)(givenName=*$word*))(".$f_entries['organization']."=*$word*))";
+        $filter .= '(|(|('.$FIELDS['name'].'=*'.$word.'*)('.
+                   $FIELDS['givenname'].'=*'.$word.'*))('.
+                   $FIELDS['organization'].'=*'.$word.'*))';
       }
       $ldapfilter = "(&(objectClass=inetOrgPerson)$filter)";
     }elseif(!empty($org)){
-      $ldapfilter = "(&(objectClass=inetOrgPerson)(".$f_entries['organization']."=$org))";
+      // List organization members
+      $ldapfilter = '(&(objectClass=inetOrgPerson)('.$FIELDS['organization']."=$org))";
     }elseif($filter=='other'){
+      // Alphabetic listing of last names
       $other='';
       for ($i=ord('a');$i<=ord('z');$i++){
-        $other .= '(!(sn='.chr($i).'*))';
+        $other .= '(!('.$FIELDS['name'].'='.chr($i).'*))';
       }
       $ldapfilter = "(&(objectClass=inetOrgPerson)$other)";
     }elseif($filter=='*'){
+      // List all
       $ldapfilter = "(objectClass=inetOrgPerson)";
     }else{
-      $ldapfilter = "(&(objectClass=inetOrgPerson)(sn=$filter*))";
+      // Search by last name start
+      $ldapfilter = '(&(objectClass=inetOrgPerson)('.$FIELDS['name']."=$filter*))";
     }
     return $ldapfilter;
   }
index b46c6dca9cae1d1deb159a13a8de9c8d6746b0bb..9bd00d2e916282635f4b7414c7ed672ab147816a 100644 (file)
--- a/init.php
+++ b/init.php
@@ -1,5 +1,6 @@
 <?
   require_once('config.php');
+  require_once('fields.php');
   require_once('lang/'.$conf['lang'].'.php');
   require_once('functions.php');
   require_once('template.php');
@@ -8,7 +9,7 @@
   //init session
   session_name("ldapab");
   session_start();
-  
+
   //kill magic quotes
   if (get_magic_quotes_gpc()) {
     if (!empty($_GET))    remove_magic_quotes($_GET);
@@ -27,8 +28,8 @@
       }else {
         $array[$key] = stripslashes($array[$key]);
       }
-    } 
-  } 
+    }
+  }
 
   //prepare SMARTY object
   $smarty = new Smarty;
index 443ed67cd8271ff8539ef4263aadfe91fb9a8a0a..da161203ba55de6f3bae558b9749ae37bbf5f966 100644 (file)
--- a/tags.php
+++ b/tags.php
   function tag_cloud(){
     global $conf;
     global $LDAP_CON;
+    global $FIELDS;
     if(!$conf['extended']) return;
 
-    $result = ldap_queryabooks('(objectClass=contactPerson)','marker');
+    $result = ldap_queryabooks('(objectClass=inetOrgPerson)',$FIELDS['*marker']);
 
     $max = 0;
     $min = 999999999;
     $tags = array();
     foreach ($result as $entry){
-      if(!empty($entry['marker']) && count($entry['marker'])){
-        foreach($entry['marker'] as $marker){
+      if(!empty($entry[$FIELDS['*marker']]) && count($entry[$FIELDS['*marker']])){
+        foreach($entry[$FIELDS['*marker']] as $marker){
           $marker = strtolower($marker);
           if (empty($tags[$marker])) { $tags[$marker]=0; }
           $tags[$marker] += 1;
index df83800a0d8925b1d8911b918331ed89ce653a4a..03be9a9449b94fe1f6595599bb8840e85febcd41 100644 (file)
@@ -7,6 +7,7 @@ function tpl_std(){
   global $smarty;
   global $lang;
   global $conf;
+  global $FIELDS;
 
   if(empty($_SESSION['ldapab']['username'])){
     $_SESSION['ldapab']['username'] = '';
@@ -24,7 +25,7 @@ function tpl_std(){
   }
   $smarty->assign('conf',$conf);
   $smarty->assign('lang',$lang);
-  //$smarty->assign('dfexample',$dfexample);
+  $smarty->assign('fields',$FIELDS);
 }
 
 /**
@@ -34,49 +35,51 @@ function tpl_std(){
 function tpl_entry($in){
   global $smarty;
   global $conf;
-  $entries = namedentries();
+  global $RFIELDS;
+
   $out=array();
 
-  //handle named entries
-  foreach(array_keys($entries) as $key){
-    if(!empty($in[$key])){
-      if(is_array($in[$key])){
-        $out[$entries[$key]] = $in[$key][0];
-      }else{
-        $out[$entries[$key]] = $in[$key];
-      }
+  // handle named entries
+  foreach($RFIELDS as $key => $name){
+    if(empty($in[$key])) continue;
+
+    // keep arrays for starred fields
+    if($name{0} == '*'){
+        $name  = substr($name,1);
+        if(is_array($in[$key])){
+            $out[$name] = $in[$key];
+        }else{
+            $out[$name] = array($in[$key]);
+        }
+    }else{
+        if(is_array($in[$key])){
+            $out[$name] = $in[$key][0];
+        }else{
+            $out[$name] = $in[$key];
+        }
     }
   }
 
-  //set the type
+  // set the type
   if (empty($out['dn'])) { $out['dn']=''; }
   $out['dn']          = normalize_dn($out['dn']);
   $conf['publicbook'] = normalize_dn($conf['publicbook']);
   if($out['dn']){
-    if(strstr($out['dn'],$conf['publicbook'])){
-      $out['type'] = 'public';
-    }else{
-      $out['type'] = 'private';
-    }
+      if(strstr($out['dn'],$conf['publicbook'])){
+          $out['type'] = 'public';
+      }else{
+          $out['type'] = 'private';
+      }
   }
 
-  //mail entries are handled specially
-  if (empty($in['mail'])) { $in['mail']=''; }
-  $out['mail'] = $in['mail'];
-  if ($conf['extended']){
-    //handle marker specially in extended mode
-    if (empty($in['marker'])) { $in['marker']=''; }
-    $out['marker'] = $in['marker'];
-    if(is_array($in['marker'])) $out['markers'] = join(', ',$in['marker']);
-  }
-  if ($conf['openxchange']){
-    //handle categories specially in openxchange mode
-    $out['categories'] = $in['OXUserCategories'];
-  }
+  // join marker field to markers
+  if(is_array($out['marker'])) $out['markers'] = join(', ',$out['marker']);
 
-/*print '<pre>';
+/*
+print '<pre>';
 print_r($out);
-print '</pre>';*/
+print '</pre>';
+*/
 
   $smarty->assign('entry',$out);
 }
@@ -135,7 +138,7 @@ function tpl_markers(){
   }
   $markers = array_unique($markers);
   sort($markers,SORT_STRING);
+
   $smarty->assign('markers',$markers);
 }
 
@@ -146,31 +149,30 @@ function tpl_orgs(){
   global $conf;
   global $LDAP_CON;
   global $smarty;
+  global $FIELDS;
 
-  $f_entries = namedentries(true);
-  
   $orgs = array();
 
-  $sr = ldap_list($LDAP_CON,$conf['publicbook'],"ObjectClass=inetOrgPerson",array($f_entries['organization']));
+  $sr = ldap_list($LDAP_CON,$conf['publicbook'],"ObjectClass=inetOrgPerson",array($FIELDS['organization']));
   $result1 = ldap_get_binentries($LDAP_CON, $sr);
   //check users private addressbook
   if(!empty($_SESSION['ldapab']['binddn'])){
     $sr = @ldap_list($LDAP_CON,
                     $conf['privatebook'].','.$_SESSION['ldapab']['binddn'],
-                    "ObjectClass=inetOrgPerson",array($f_entries['organization']));
+                    "ObjectClass=inetOrgPerson",array($FIELDS['organization']));
     $result2 = ldap_get_binentries($LDAP_CON, $sr);
   }
   $result = array_merge((array)$result1,(array)$result2);
 
   if(count($result)){
     foreach ($result as $entry){
-      if(!empty($entry[$f_entries['organization']][0])){
-        array_push($orgs, $entry[$f_entries['organization']][0]);
+      if(!empty($entry[$FIELDS['organization']][0])){
+        array_push($orgs, $entry[$FIELDS['organization']][0]);
       }
     }
   }
   $orgs = array_unique($orgs);
-  sort($orgs,SORT_STRING);
+  natcasesort($orgs);
   $smarty->assign('orgs',$orgs);
 }
 
@@ -208,7 +210,7 @@ function tpl_categories(){
   }
   $categories = array_unique($categories);
   sort($categories,SORT_STRING);
+
   $smarty->assign('categories',$categories);
 }
 
@@ -246,7 +248,7 @@ function tpl_timezone(){
   }
   $timezone = array_unique($timezone);
   sort($timezone,SORT_STRING);
+
   $smarty->assign('timezone',$timezone);
 }
 
@@ -284,7 +286,7 @@ function tpl_country(){
   }
   $country = array_unique($country);
   sort($country,SORT_STRING);
+
   $smarty->assign('country',$country);
 }
 
index 816ebddc888a862bea0fa0bea9245b1296bb8a88..f01d8066a8ca4570a928d768a1f8c927a3f813e0 100644 (file)
       {/if}
       </table>
     </td>
-    
+
     <td valign="top" width="50%" align="center">
-      
-      
+
+
       <table>
         <tr>
           <td colspan="2"><b>{$lang.private}</b></td>
           <td align="right" valign="top" nowrap="nowrap">{$lang.url}:</td>
           <td><input type="text" class="input" name="entry[url]" value="{$entry.url|escape}"></td>
         </tr>
-        
+
         <tr>
           <td align="right" valign="top" nowrap="nowrap">{$lang.photo}:<br /><span class="hint">({$lang.msg_jpegonly})</span></td>
           <td>{if $entry.photo}
             <input type="file" class="input" name="photoupload">
           </td>
         </tr>
-        
+
 
         <tr>
           <td colspan="2"><b>{$lang.mail}</b></td>
         </tr>
 
-        {foreach from=$entry.mail item=mail}      
+        {foreach from=$entry.mail item=mail}
           <tr>
             <td align="right" valign="top" nowrap="nowrap">{counter}:</td>
             <td><input type="text" class="input" name="entry[mail][]" value="{$mail|escape}"></td>