Converted all files in functions/ to use SM_PATH. This will break all
[squirrelmail.git] / functions / addressbook.php
index 269b199417a630682ea9462f30ee73865816d6a2..f62ccde50fdc0f752c2968d05edf02a7d4864ea2 100644 (file)
  * Copyright (c) 1999-2002 The SquirrelMail Project Team
  * Licensed under the GNU GPL. For full terms see the file COPYING.
  *
- * Manage personal address book.
+ * Functions and classes for the addressbook system.
  *
  * $Id$
  */
 
-require_once('../src/validate.php');
-require_once('../functions/array.php');
-require_once('../functions/display_messages.php');
-require_once('../functions/addressbook.php');
-require_once('../functions/strings.php');
-require_once('../functions/html.php');
-
-/* Make an input field */
-function adressbook_inp_field($label, $field, $name, $size, $values, $add) {
-    global $color;
-    $td_str = '<INPUT NAME="' . $name . '[' . $field . ']" SIZE="' . $size . '" VALUE="';
-    if (isset($values[$field])) {
-        $td_str .= htmlspecialchars($values[$field]);
-    }
-    $td_str .= '">' . $add . '';
-    return html_tag( 'tr' ,
-        html_tag( 'td', $label . ':', 'right', $color[4]) .
-        html_tag( 'td', $td_str, 'left', $color[4])
-        )
-    . "\n";
+/*
+   This is the path to the global site-wide addressbook.
+   It looks and feels just like a user's .abook file
+   If this is in the data directory, use "$data_dir/global.abook"
+   If not, specify the path as though it was accessed from the
+   src/ directory ("../global.abook" -> in main directory)
+
+   If you don't want a global site-wide addressbook, comment these
+   two lines out.  (They are disabled by default.)
+
+   The global addressbook is unmodifiable by anyone.  You must actually
+   use a shell script or whatnot to modify the contents.
+
+  global $data_dir;
+  $address_book_global_filename = "$data_dir/global.abook";
+
+  Include backends here.
+*/
+
+require_once(SM_PATH . 'functions/abook_local_file.php');
+require_once(SM_PATH . 'functions/abook_ldap_server.php');
+
+global $addrbook_dsn;
+
+/* Use this if you wanna have a global address book */
+if (isset($address_book_global_filename)) {
+    include_once('../functions/abook_global_file.php');
 }
 
-/* Output form to add and modify address data */
-function address_form($name, $submittext, $values = array()) {
-    global $color;
-    echo html_tag( 'table',
-                       adressbook_inp_field(_("Nickname"),     'nickname', $name, 15, $values,
-                           '<SMALL>' . _("Must be unique") . '</SMALL>') .
-                       adressbook_inp_field(_("E-mail address"),  'email', $name, 45, $values, '') .
-                       adressbook_inp_field(_("First name"),  'firstname', $name, 45, $values, '') .
-                       adressbook_inp_field(_("Last name"),    'lastname', $name, 45, $values, '') .
-                       adressbook_inp_field(_("Additional info"), 'label', $name, 45, $values, '') .
-                       html_tag( 'tr',
-                           html_tag( 'td',
-                                       '<INPUT TYPE=submit NAME="' . $name . '[SUBMIT]" VALUE="' .
-                                       $submittext . '">',
-                                   'center', $color[4], 'colspan="2"')
-                       )
-    , 'center', '', 'border="0" cellpadding="1" cols="2" width="90%"') ."\n";
+/* Only load database backend if database is configured */
+if(isset($addrbook_dsn) && !empty($addrbook_dsn)) {
+  include_once('../functions/abook_database.php');
 }
 
+/*
+   Create and initialize an addressbook object.
+   Returns the created object
+*/
+function addressbook_init($showerr = true, $onlylocal = false) {
+    global $data_dir, $username, $ldap_server, $address_book_global_filename;
+    global $addrbook_dsn, $addrbook_table;
+
+    /* Create a new addressbook object */
+    $abook = new AddressBook;
+
+    /*
+        Always add a local backend. We use *either* file-based *or* a
+        database addressbook. If $addrbook_dsn is set, the database
+        backend is used. If not, addressbooks are stores in files.
+    */
+    if (isset($addrbook_dsn) && !empty($addrbook_dsn)) {
+        /* Database */
+        if (!isset($addrbook_table) || empty($addrbook_table)) {
+            $addrbook_table = 'address';
+        }
+        $r = $abook->add_backend('database', Array('dsn' => $addrbook_dsn,
+                            'owner' => $username,
+                            'table' => $addrbook_table));
+        if (!$r && $showerr) {
+            echo _("Error initializing addressbook database.");
+            exit;
+        }
+    } else {
+        /* File */
+        $filename = getHashedFile($username, $data_dir, "$username.abook");
+        $r = $abook->add_backend('local_file', Array('filename' => $filename,
+                              'create'   => true));
+        if(!$r && $showerr) {
+            printf( _("Error opening file %s"), $filename );
+            exit;
+        }
+
+    }
+
+    /* This would be for the global addressbook */
+    if (isset($address_book_global_filename)) {
+        $r = $abook->add_backend('global_file');
+        if (!$r && $showerr) {
+            echo _("Error initializing global addressbook.");
+            exit;
+        }
+    }
 
-/* Open addressbook, with error messages on but without LDAP (the *
- * second "true"). Don't need LDAP here anyway                    */
-$abook = addressbook_init(true, true);
-if($abook->localbackend == 0) {
-    plain_error_message(
-        _("No personal address book is defined. Contact administrator."),
-        $color);
-    exit();
+    if ($onlylocal) {
+        return $abook;
+    }
+
+    /* Load configured LDAP servers (if PHP has LDAP support) */
+    if (isset($ldap_server) && is_array($ldap_server) && function_exists('ldap_connect')) {
+        reset($ldap_server);
+        while (list($undef,$param) = each($ldap_server)) {
+            if (is_array($param)) {
+                $r = $abook->add_backend('ldap_server', $param);
+                if (!$r && $showerr) {
+                    printf( '&nbsp;' . _("Error initializing LDAP server %s:") .
+                            "<BR>\n", $param['host']);
+                    echo '&nbsp;' . $abook->error;
+                    exit;
+                }
+            }
+        }
+    }
+
+    /* Return the initialized object */
+    return $abook;
 }
 
-displayPageHeader($color, 'None');
 
+/*
+ *   Had to move this function outside of the Addressbook Class
+ *   PHP 4.0.4 Seemed to be having problems with inline functions.
+ */    
+function addressbook_cmp($a,$b) {
+
+    if($a['backend'] > $b['backend']) {
+        return 1;
+    } else if($a['backend'] < $b['backend']) {
+        return -1;
+    }
+    
+    return (strtolower($a['name']) > strtolower($b['name'])) ? 1 : -1;
+
+}
 
-$defdata   = array();
-$formerror = '';
-$abortform = false;
-$showaddrlist = true;
-$defselected  = array();
 
+/*
+ * This is the main address book class that connect all the
+ * backends and provide services to the functions above.
+ *
+ */
 
-/* Handle user's actions */
-if($REQUEST_METHOD == 'POST') {
+class AddressBook {
 
-    /**************************************************
-     * Add new address                                *
-     **************************************************/
-    if (!empty($addaddr['nickname'])) {
+    var $backends    = array();
+    var $numbackends = 0;
+    var $error       = '';
+    var $localbackend = 0;
+    var $localbackendname = '';
+    
+      // Constructor function.
+    function AddressBook() {
+        $localbackendname = _("Personal address book");
+    }
 
-        $r = $abook->add($addaddr, $abook->localbackend);
+    /*
+     * Return an array of backends of a given type,
+     * or all backends if no type is specified.
+     */
+    function get_backend_list($type = '') {
+        $ret = array();
+        for ($i = 1 ; $i <= $this->numbackends ; $i++) {
+            if (empty($type) || $type == $this->backends[$i]->btype) {
+                $ret[] = &$this->backends[$i];
+            }
+        }
+        return $ret;
+    }
 
-        /* Handle error messages */
-        if (!$r) {
-            /* Remove backend name from error string */
-            $errstr = $abook->error;
-            $errstr = ereg_replace('^\[.*\] *', '', $errstr);
 
-            $formerror = $errstr;
-            $showaddrlist = false;
-            $defdata = $addaddr;
+    /*
+       ========================== Public ========================
+
+        Add a new backend. $backend is the name of a backend
+        (without the abook_ prefix), and $param is an optional
+        mixed variable that is passed to the backend constructor.
+        See each of the backend classes for valid parameters.
+     */
+    function add_backend($backend, $param = '') {
+        $backend_name = 'abook_' . $backend;
+        eval('$newback = new ' . $backend_name . '($param);');
+        if(!empty($newback->error)) {
+            $this->error = $newback->error;
+            return false;
         }
 
-    } else {
+        $this->numbackends++;
 
-        /************************************************
-         * Delete address(es)                           *
-         ************************************************/
-        if ((!empty($deladdr)) && sizeof($sel) > 0) {
-            $orig_sel = $sel;
-            sort($sel);
-
-            /* The selected addresses are identidied by "backend:nickname". *
-             * Sort the list and process one backend at the time            */
-            $prevback  = -1;
-            $subsel    = array();
-            $delfailed = false;
-
-            for ($i = 0 ; (($i < sizeof($sel)) && !$delfailed) ; $i++) {
-                list($sbackend, $snick) = explode(':', $sel[$i]);
-
-                /* When we get to a new backend, process addresses in *
-                 * previous one.                                      */
-                if ($prevback != $sbackend && $prevback != -1) {
-
-                    $r = $abook->remove($subsel, $prevback);
-                    if (!$r) {
-                        $formerror = $abook->error;
-                        $i = sizeof($sel);
-                        $delfailed = true;
-                        break;
-                    }
-                    $subsel   = array();
-                }
+        $newback->bnum = $this->numbackends;
+        $this->backends[$this->numbackends] = $newback;
+        
+        /* Store ID of first local backend added */
+        if ($this->localbackend == 0 && $newback->btype == 'local') {
+            $this->localbackend = $this->numbackends;
+            $this->localbackendname = $newback->sname;
+        }
 
-                /* Queue for processing */
-                array_push($subsel, $snick);
-                $prevback = $sbackend;
-            }
+        return $this->numbackends;
+    }
 
-            if (!$delfailed) {
-                $r = $abook->remove($subsel, $prevback);
-                if (!$r) { /* Handle errors */
-                    $formerror = $abook->error;
-                    $delfailed = true;
-                }
-            }
 
-            if ($delfailed) {
-                $showaddrlist = true;
-                $defselected  = $orig_sel;
-            }
+    /*
+     * This function takes a $row array as returned by the addressbook 
+     * search and returns an e-mail address with the full name or
+     * nickname optionally prepended.
+     */
 
+    function full_address($row) {
+        global $addrsrch_fullname, $datadir, $user;
+
+        if (($prefix = getPref($datadir, $user, 'addrsrch_fullname') or
+            isset($addrsrch_fullname) and $prefix = $addrsrch_fullname)
+            and $prefix !== 'noprefix') {
+            $name = ($prefix === 'nickname') ? $row['nickname']
+                : $row['name'];
+            return $name . ' <' . trim($row['email']) . '>';
         } else {
+            return trim($row['email']);
+        }
+    }
 
-            /***********************************************
-             * Update/modify address                       *
-             ***********************************************/
-            if (!empty($editaddr)) {
-
-                /* Stage one: Copy data into form */
-                if (isset($sel) && sizeof($sel) > 0) {
-                    if(sizeof($sel) > 1) {
-                        $formerror = _("You can only edit one address at the time");
-                        $showaddrlist = true;
-                        $defselected = $sel;
-                    } else {
-                        $abortform = true;
-                        list($ebackend, $enick) = explode(':', $sel[0]);
-                        $olddata = $abook->lookup($enick, $ebackend);
-
-                        /* Display the "new address" form */
-                        echo '<FORM ACTION="' . $PHP_SELF . '" METHOD="POST">' .
-                             "\n" .
-                             html_tag( 'table',
-                                html_tag( 'tr',
-                                   html_tag( 'td',
-                                      "\n". '<strong>' . _("Update address") . '</strong>' ."\n",
-                                      'center', $color[0] )
-                                   ),
-                             'center', '', 'width="100%" cols="1"' ) .
-                        address_form("editaddr", _("Update address"), $olddata);
-                        echo '<INPUT TYPE=hidden NAME=oldnick VALUE="' . 
-                             htmlspecialchars($olddata["nickname"]) . "\">\n" .
-                             '<INPUT TYPE=hidden NAME=backend VALUE="' .
-                             htmlspecialchars($olddata["backend"]) . "\">\n" .
-                             '<INPUT TYPE=hidden NAME=doedit VALUE=1>' . "\n" .
-                             '</FORM>';
-                    }
+    /*
+        Return a list of addresses matching expression in
+        all backends of a given type.
+    */
+    function search($expression, $bnum = -1) {
+        $ret = array();
+        $this->error = '';
+
+        /* Search all backends */
+        if ($bnum == -1) {
+            $sel = $this->get_backend_list('');
+            $failed = 0;
+            for ($i = 0 ; $i < sizeof($sel) ; $i++) {
+                $backend = &$sel[$i];
+                $backend->error = '';
+                $res = $backend->search($expression);
+                if (is_array($res)) {
+                    $ret = array_merge($ret, $res);
                 } else {
-
-                    /* Stage two: Write new data */
-                    if ($doedit = 1) {
-                        $newdata = $editaddr;
-                        $r = $abook->modify($oldnick, $newdata, $backend);
-
-                        /* Handle error messages */
-                        if (!$r) {
-                            /* Display error */
-                             echo html_tag( 'table',
-                                html_tag( 'tr',
-                                   html_tag( 'td',
-                                      "\n". '<br><strong><font color="' . $color[2] .
-                                      '">' . _("ERROR") . ': ' . $abook->error . '</font></strong>' ."\n",
-                                      'center' )
-                                   ),
-                             'center', '', 'width="100%" cols="1"' );
-
-                            /* Display the "new address" form again */
-                            echo '<FORM ACTION="' . $PHP_SELF .
-                                 '" METHOD="POST">' . "\n" .
-                                 html_tag( 'table',
-                                     html_tag( 'tr',
-                                         html_tag( 'td',
-                                                    "\n". '<br><strong>' . _("Update address") . '</strong>' ."\n",
-                                         'center', $color[0] )
-                                     ),
-                                 'center', '', 'width="100%" cols="1"' ) .
-                            address_form("editaddr", _("Update address"), $newdata);
-                            echo '<INPUT TYPE=hidden NAME=oldnick VALUE="' .
-                                 htmlspecialchars($oldnick) . "\">\n" .
-                                 '<INPUT TYPE=hidden NAME=backend VALUE="' .
-                                 htmlspecialchars($backend) . "\">\n" .
-                                 '<INPUT TYPE=hidden NAME=doedit VALUE=1>' .
-                                 "\n" . '</FORM>';
-                            $abortform = true;
-                        }
-                    } else {
-
-                        /* Should not get here... */
-                        plain_error_message(_("Unknown error"), $color);
-                        $abortform = true;
-                    }
+                    $this->error .= "<br>\n" . $backend->error;
+                    $failed++;
                 }
-            } /* !empty($editaddr)                  - Update/modify address */
-        } /* (!empty($deladdr)) && sizeof($sel) > 0 - Delete address(es) */
-    } /* !empty($addaddr['nickname'])               - Add new address */
-
-    // Some times we end output before forms are printed
-    if($abortform) {
-       echo "</BODY></HTML>\n";
-       exit();
-    }
-}
+            }
 
+            /* Only fail if all backends failed */
+            if( $failed >= sizeof( $sel ) ) {
+                $ret = FALSE;
+            }
 
-/* =================================================================== *
- * The following is only executed on a GET request, or on a POST when  *
- * a user is added, or when "delete" or "modify" was successful.       *
- * =================================================================== */
-
-/* Display error messages */
-if (!empty($formerror)) {
-    echo html_tag( 'table',
-        html_tag( 'tr',
-            html_tag( 'td',
-                   "\n". '<br><strong><font color="' . $color[2] .
-                   '">' . _("ERROR") . ': ' . $formerror . '</font></strong>' ."\n",
-            'center' )
-        ),
-    'center', '', 'width="100%" cols="1"' );
-}
+        }  else {
 
+            /* Search only one backend */
 
-/* Display the address management part */
-if ($showaddrlist) {
-    /* Get and sort address list */
-    $alist = $abook->list_addr();
-    if(!is_array($alist)) {
-        plain_error_message($abook->error, $color);
-        exit;
-    }
+            $ret = $this->backends[$bnum]->search($expression);
+            if (!is_array($ret)) {
+                $this->error .= "<br>\n" . $this->backends[$bnum]->error;
+                $ret = FALSE;
+            }
+        }
 
-    usort($alist,'alistcmp');
-    $prevbackend = -1;
-    $headerprinted = false;
+        return( $ret );
+    }
 
-    echo html_tag( 'p', '<a href="#AddAddress">' . _("Add address") . '</a>', 'center' ) . "\n";
 
-    /* List addresses */
-    if (count($alist) > 0) {
-        echo '<FORM ACTION="' . $PHP_SELF . '" METHOD="POST">' . "\n";
-        while(list($undef,$row) = each($alist)) {
-    
-            /* New table header for each backend */
-            if($prevbackend != $row['backend']) {
-                if($prevbackend < 0) {
-                    echo html_tag( 'table',
-                                    html_tag( 'tr',
-                                          html_tag( 'td',
-                                                     '<INPUT TYPE=submit NAME=editaddr VALUE="' . 
-                                                     _("Edit selected") . "\">\n" .
-                                                     '<INPUT TYPE=submit NAME=deladdr VALUE="' .
-                                                     _("Delete selected") . "\">\n",
-                                          'center', '', 'colspan="5"' )
-                                    ) .
-                                    html_tag( 'tr',
-                                          html_tag( 'td', '&nbsp;<br>', 'center', '', 'colspan="5"' )
-                                    ) ,
-                             'center' );
-                }
-    
-                echo html_tag( 'table',
-                                html_tag( 'tr',
-                                    html_tag( 'td', "\n" . '<strong>' . $row['source'] . '</strong>' . "\n", 'center', $color[0] )
-                                ) ,
-                        'center', '', 'width="95%" cols="1"' ) ."\n"
-                . html_tag( 'table', '', 'center', '', 'cols="5" border="0" cellpadding="1" cellspacing="0" width="90%"' ) .
-                      html_tag( 'tr', "\n" .
-                          html_tag( 'th', '&nbsp;', 'left', '', 'width="1%"' ) .
-                          html_tag( 'th', _("Nickname"), 'left', '', 'width="1%"' ) .
-                          html_tag( 'th', _("Name"), 'left', '', 'width="1%"' ) .
-                          html_tag( 'th', _("E-mail"), 'left', '', 'width="1%"' ) .
-                          html_tag( 'th', _("Info"), 'left', '', 'width="1%"' ) ,
-                      '', $color[9] ) . "\n";
+    /* Return a sorted search */
+    function s_search($expression, $bnum = -1) {
     
-                $line = 0;
-                $headerprinted = true;
-            } /* End of header */
+        $ret = $this->search($expression, $bnum);
+        if ( is_array( $ret ) ) {
+            usort($ret, 'addressbook_cmp');
+        }    
+        return $ret;
+    }
+
+
+    /*
+     *  Lookup an address by alias. Only possible in
+     *  local backends.
+     */
+    function lookup($alias, $bnum = -1) {
     
-            $prevbackend = $row['backend'];
+        $ret = array();
     
-            /* Check if this user is selected */
-            if(in_array($row['backend'] . ':' . $row['nickname'], $defselected)) {
-                $selected = 'CHECKED';
+        if ($bnum > -1) {
+            $res = $this->backends[$bnum]->lookup($alias);
+            if (is_array($res)) {
+               return $res;
             } else {
-                $selected = '';
+               $this->error = $backend->error;
+               return false;
             }
+        }
     
-            /* Print one row */
-            $tr_bgcolor = '';
-            if ($line % 2) { $tr_bgcolor = $color[0]; }
-            echo html_tag( 'tr', '') .
-            html_tag( 'td',
-                '<SMALL>' .
-                '<INPUT TYPE=checkbox ' . $selected . ' NAME="sel[]" VALUE="' .
-                $row['backend'] . ':' . $row['nickname'] . '"></SMALL>' ,
-                'center', '', 'valign="top" width="1%"' ) .
-            html_tag( 'td', '&nbsp;' . $row['nickname'] . '&nbsp;', 'left', '', 'valign="top" width="1%" nowrap' ) .
-            html_tag( 'td', '&nbsp;' . $row['name'] . '&nbsp;', 'left', '', 'valign="top" width="1%" nowrap' ) .
-            html_tag( 'td', '', 'left', '', 'valign="top" width="1%" nowrap' ) . '&nbsp;';
-            if ($compose_new_win == '1') {
-                echo '<a href="javascript:void(0)" onclick=comp_in_new(false,"compose.php?send_to='.rawurlencode($row['email']).'")>';
+        $sel = $this->get_backend_list('local');
+        for ($i = 0 ; $i < sizeof($sel) ; $i++) {
+            $backend = &$sel[$i];
+            $backend->error = '';
+            $res = $backend->lookup($alias);
+            if (is_array($res)) {
+               if(!empty($res))
+              return $res;
+            } else {
+               $this->error = $backend->error;
+               return false;
             }
-            else {
-                echo '<A HREF="compose.php?send_to=' . rawurlencode($row['email']).'">';
+        }
+        
+        return $ret;
+    }
+
+
+    /* Return all addresses */
+    function list_addr($bnum = -1) {
+        $ret = array();
+        
+        if ($bnum == -1) {
+            $sel = $this->get_backend_list('local');
+        } else {
+            $sel = array(0 => &$this->backends[$bnum]);
+        }
+        
+        for ($i = 0 ; $i < sizeof($sel) ; $i++) {
+            $backend = &$sel[$i];
+            $backend->error = '';
+            $res = $backend->list_addr();
+            if (is_array($res)) {
+               $ret = array_merge($ret, $res);
+            } else {
+               $this->error = $backend->error;
+               return false;
             }
-            echo $row['email'] . '</A>&nbsp;</td>'."\n".
-            html_tag( 'td', '&nbsp;' . $row['label'] . '&nbsp;', 'left', '', 'valign="top" width="1%"' ) .
-            "</tr>\n";
-            $line++;
         }
+        
+        return $ret;
+    }
+
+    /*
+     * Create a new address from $userdata, in backend $bnum.
+     * Return the backend number that the/ address was added
+     * to, or false if it failed.
+     */
+    function add($userdata, $bnum) {
+    
+        /* Validate data */
+        if (!is_array($userdata)) {
+            $this->error = _("Invalid input data");
+            return false;
+        }
+        if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
+            $this->error = _("Name is missing");
+            return false;
+        }
+        if (empty($userdata['email'])) {
+            $this->error = _("E-mail address is missing");
+            return false;
+        }
+        if (empty($userdata['nickname'])) {
+            $userdata['nickname'] = $userdata['email'];
+        }
+        
+        if (eregi('[ \\:\\|\\#\\"\\!]', $userdata['nickname'])) {
+            $this->error = _("Nickname contains illegal characters");
+            return false;
+        }
+        
+        /* Check that specified backend accept new entries */
+        if (!$this->backends[$bnum]->writeable) {
+            $this->error = _("Addressbook is read-only");
+            return false;
+        }
+        
+        /* Add address to backend */
+        $res = $this->backends[$bnum]->add($userdata);
+        if ($res) {
+            return $bnum;
+        } else {
+            $this->error = $this->backends[$bnum]->error;
+            return false;
+        }
+        
+        return false;  // Not reached
+    } /* end of add() */
+
+
+    /*
+     * Remove the user identified by $alias from backend $bnum
+     * If $alias is an array, all users in the array are removed.
+     */
+    function remove($alias, $bnum) {
+    
+        /* Check input */
+        if (empty($alias)) {
+            return true;
+        }
+        
+        /* Convert string to single element array */
+        if (!is_array($alias)) {
+            $alias = array(0 => $alias);
+        }
+        
+        /* Check that specified backend is writable */
+        if (!$this->backends[$bnum]->writeable) {
+            $this->error = _("Addressbook is read-only");
+            return false;
+        }
+        
+        /* Remove user from backend */
+        $res = $this->backends[$bnum]->remove($alias);
+        if ($res) {
+            return $bnum;
+        } else {
+            $this->error = $this->backends[$bnum]->error;
+            return false;
+        }
+        
+        return FALSE;  /* Not reached */
+    } /* end of remove() */
+
+
+    /*
+     * Remove the user identified by $alias from backend $bnum
+     * If $alias is an array, all users in the array are removed.
+     */
+    function modify($alias, $userdata, $bnum) {
+    
+        /* Check input */
+        if (empty($alias) || !is_string($alias)) {
+            return true;
+        }
+        
+        /* Validate data */
+        if(!is_array($userdata)) {
+            $this->error = _("Invalid input data");
+            return false;
+        }
+        if (empty($userdata['firstname']) && empty($userdata['lastname'])) {
+            $this->error = _("Name is missing");
+            return false;
+        }
+        if (empty($userdata['email'])) {
+            $this->error = _("E-mail address is missing");
+            return false;
+        }
+        
+        if (eregi('[\\: \\|\\#"\\!]', $userdata['nickname'])) {
+            $this->error = _("Nickname contains illegal characters");
+            return false;
+        }
+        
+        if (empty($userdata['nickname'])) {
+            $userdata['nickname'] = $userdata['email'];
+        }
+        
+        /* Check that specified backend is writable */
+        if (!$this->backends[$bnum]->writeable) {
+            $this->error = _("Addressbook is read-only");;
+            return false;
+        }
+        
+        /* Modify user in backend */
+        $res = $this->backends[$bnum]->modify($alias, $userdata);
+        if ($res) {
+            return $bnum;
+        } else {
+            $this->error = $this->backends[$bnum]->error;
+            return false;
+        }
+        
+        return FALSE;  /* Not reached */
+    } /* end of modify() */
+    
+    
+} /* End of class Addressbook */
+
+/*
+ * Generic backend that all other backends extend
+ */
+class addressbook_backend {
+
+    /* Variables that all backends must provide. */
+    var $btype      = 'dummy';
+    var $bname      = 'dummy';
+    var $sname      = 'Dummy backend';
     
-        /* End of list. Close table. */
-        if ($headerprinted) {
-            echo html_tag( 'tr',
-                        html_tag( 'td',
-                                '<INPUT TYPE="submit" NAME="editaddr" VALUE="' . _("Edit selected") .
-                                "\">\n" .
-                                '<INPUT TYPE="submit" NAME="deladdr" VALUE="' . _("Delete selected") .
-                                "\">\n",
-                         'center', '', 'colspan="5"' )
-                    );
-        }
-        echo '</table></FORM>';
+    /*
+     * Variables common for all backends, but that
+     * should not be changed by the backends.
+     */
+    var $bnum       = -1;
+    var $error      = '';
+    var $writeable  = false;
+    
+    function set_error($string) {
+        $this->error = '[' . $this->sname . '] ' . $string;
+        return false;
+    }
+    
+    
+    /* ========================== Public ======================== */
+    
+    function search($expression) {
+        $this->set_error('search not implemented');
+        return false;
+    }
+    
+    function lookup($alias) {
+        $this->set_error('lookup not implemented');
+        return false;
+    }
+    
+    function list_addr() {
+        $this->set_error('list_addr not implemented');
+        return false;
+    }
+    
+    function add($userdata) {
+        $this->set_error('add not implemented');
+        return false;
+    }
+    
+    function remove($alias) {
+        $this->set_error('delete not implemented');
+        return false;
+    }
+    
+    function modify($alias, $newuserdata) {
+        $this->set_error('modify not implemented');
+        return false;
     }
-} /* end of addresslist */
-
-
-/* Display the "new address" form */
-echo '<a name="AddAddress"></a>' . "\n" .
-    '<FORM ACTION="' . $PHP_SELF . '" NAME=f_add METHOD="POST">' . "\n" .
-    html_tag( 'table',
-        html_tag( 'tr',
-            html_tag( 'td', "\n". '<strong>' . sprintf(_("Add to %s"), $abook->localbackendname) . '</strong>' . "\n",
-                'center', $color[0]
-            )
-        )
-    , 'center', '', 'width="100%" cols="1"' ) ."\n";
-address_form('addaddr', _("Add address"), $defdata);
-echo '</FORM>';
-
-/* Add hook for anything that wants on the bottom */
-do_hook('addressbook_bottom');
-?>
 
-</BODY></HTML>
\ No newline at end of file
+}
+
+/* Sort array by the key "name" */
+function alistcmp($a,$b) {
+    if ($a['backend'] > $b['backend']) {
+        return 1;
+    } else {
+        if ($a['backend'] < $b['backend']) {
+            return -1;
+        }
+    }
+    return (strtolower($a['name']) > strtolower($b['name'])) ? 1 : -1;
+}
+
+?>