Source for file addressbook.php

Documentation is available at addressbook.php

  1. <?php
  2. /**
  3.  * functions/addressbook.php - Functions and classes for the addressbook system
  4.  *
  5.  * Functions require SM_PATH and support of forms.php functions
  6.  *
  7.  * @copyright &copy; 1999-2006 The SquirrelMail Project Team
  8.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  9.  * @version $Id: addressbook.php,v 1.100 2006/08/31 05:43:18 tokul Exp $
  10.  * @package squirrelmail
  11.  * @subpackage addressbook
  12.  */
  13.  
  14. /**
  15.  * Create and initialize an addressbook object.
  16.  * @param boolean $showerr display any address book init errors. html page header
  17.  *  must be created before calling addressbook_init() with $showerr enabled.
  18.  * @param boolean $onlylocal enable only local address book backends. Should
  19.  *   be used when code does not need access to remote backends. Backends
  20.  *   that provide read only address books with limited listing options can be
  21.  *   tagged as remote.
  22.  * @return object address book object.
  23.  */
  24. function addressbook_init($showerr true$onlylocal false{
  25.     global $data_dir$username$ldap_server$address_book_global_filename;
  26.     global $addrbook_dsn$addrbook_table;
  27.     global $abook_file_line_length;
  28.  
  29.     /* Create a new addressbook object */
  30.     $abook new AddressBook;
  31.  
  32.     /* Create empty error message */
  33.     $abook_init_error='';
  34.  
  35.     /*
  36.         Always add a local backend. We use *either* file-based *or* a
  37.         database addressbook. If $addrbook_dsn is set, the database
  38.         backend is used. If not, addressbooks are stores in files.
  39.     */
  40.     if (isset($addrbook_dsn&& !empty($addrbook_dsn)) {
  41.         /* Database */
  42.         if (!isset($addrbook_table|| empty($addrbook_table)) {
  43.             $addrbook_table 'address';
  44.         }
  45.         $r $abook->add_backend('database'Array('dsn' => $addrbook_dsn,
  46.                             'owner' => $username,
  47.                             'table' => $addrbook_table));
  48.         if (!$r && $showerr{
  49.             $abook_init_error.=_("Error initializing address book database.""\n" $abook->error;
  50.         }
  51.     else {
  52.         /* File */
  53.         $filename getHashedFile($username$data_dir"$username.abook");
  54.         $r $abook->add_backend('local_file'Array('filename' => $filename,
  55.                                                      'line_length' => $abook_file_line_length,
  56.                                                      'create'   => true));
  57.         if(!$r && $showerr{
  58.             // no need to use $abook->error, because message explains error.
  59.             $abook_init_error.=sprintf_("Error opening file %s")$filename );
  60.         }
  61.     }
  62.  
  63.     /* Global file based addressbook */
  64.     if (isset($abook_global_file&&
  65.         isset($abook_global_file_writeable&&
  66.         isset($abook_global_file_listing&&
  67.         trim($abook_global_file)!=''){
  68.  
  69.         // Detect place of address book
  70.         if (preg_match("/[\/\\\]/",$abook_global_file)) {
  71.             /* no path chars, address book stored in data directory
  72.              * make sure that there is a slash between data directory
  73.              * and address book file name
  74.              */
  75.             $abook_global_filename=$data_dir
  76.                 . ((substr($data_dir-1!= '/''/' '')
  77.                 . $abook_global_file;
  78.         elseif (preg_match("/^\/|\w:/",$abook_global_file)) {
  79.             // full path is set in options (starts with slash or x:)
  80.             $abook_global_filename=$abook_global_file;
  81.         else {
  82.             $abook_global_filename=SM_PATH $abook_global_file;
  83.         }
  84.  
  85.         $r $abook->add_backend('local_file',array('filename'=>$abook_global_filename,
  86.                                                     'name' => _("Global address book"),
  87.                                                     'detect_writeable' => false,
  88.                                                     'line_length' => $abook_file_line_length,
  89.                                                     'writeable'=> $abook_global_file_writeable,
  90.                                                     'listing' => $abook_global_file_listing));
  91.  
  92.         /* global abook init error is not fatal. add error message and continue */
  93.         if (!$r && $showerr{
  94.             if ($abook_init_error!=''$abook_init_error.="\n";
  95.             $abook_init_error.=_("Error initializing global address book.""\n" $abook->error;
  96.         }
  97.     }
  98.  
  99.     /* Load global addressbook from SQL if configured */
  100.     if (isset($addrbook_global_dsn&& !empty($addrbook_global_dsn)) {
  101.       /* Database configured */
  102.       if (!isset($addrbook_global_table|| empty($addrbook_global_table)) {
  103.           $addrbook_global_table 'global_abook';
  104.       }
  105.       $r $abook->add_backend('database',
  106.                                Array('dsn' => $addrbook_global_dsn,
  107.                                      'owner' => 'global',
  108.                                      'name' => _("Global address book"),
  109.                                      'writeable' => $addrbook_global_writeable,
  110.                                      'listing' => $addrbook_global_listing,
  111.                                      'table' => $addrbook_global_table));
  112.       /* global abook init error is not fatal. add error message and continue */
  113.       if (!$r && $showerr{
  114.           if ($abook_init_error!=''$abook_init_error.="\n";
  115.           $abook_init_error.=_("Error initializing global address book.""\n" $abook->error;
  116.       }
  117.     }
  118.  
  119.     /*
  120.      * hook allows to include different address book backends.
  121.      * plugins should extract $abook and $r from arguments
  122.      * and use same add_backend commands as above functions.
  123.      * Since 1.5.2 hook sends third ($onlylocal) argument to address book
  124.      * plugins in order to allow detection of local address book init.
  125.      * @since 1.5.1 and 1.4.5
  126.      */
  127.     $hookReturn do_hook('abook_init'$abook$r$onlylocal);
  128.     $abook $hookReturn[1];
  129.     $r $hookReturn[2];
  130.     if (!$r && $showerr{
  131.         if ($abook_init_error!=''$abook_init_error.="\n";
  132.         $abook_init_error.=_("Error initializing other address books.""\n" $abook->error;
  133.     }
  134.  
  135.     /* Load configured LDAP servers (if PHP has LDAP support) */
  136.     if (isset($ldap_server&& is_array($ldap_server)) {
  137.         reset($ldap_server);
  138.         while (list($undef,$parameach($ldap_server)) {
  139.             if (!is_array($param))
  140.                 continue;
  141.  
  142.             /* if onlylocal is true, we only add writeable ldap servers */
  143.             if ($onlylocal && (!isset($param['writeable']|| $param['writeable'!= true))
  144.                 continue;
  145.  
  146.             $r $abook->add_backend('ldap_server'$param);
  147.             if (!$r && $showerr{
  148.                 if ($abook_init_error!=''$abook_init_error.="\n";
  149.                 $abook_init_error.=sprintf(_("Error initializing LDAP server %s:")$param['host'])."\n";
  150.                 $abook_init_error.= $abook->error;
  151.             }
  152.         }
  153.     // end of ldap server init
  154.  
  155.     /**
  156.      * display address book init errors.
  157.      */
  158.     if ($abook_init_error!='' && $showerr{
  159.         error_box(nl2br(htmlspecialchars($abook_init_error)));
  160.     }
  161.  
  162.     /* Return the initialized object */
  163.     return $abook;
  164. }
  165.  
  166. /**
  167.  * Display the "new address" form
  168.  *
  169.  * Form is not closed and you must add closing form tag.
  170.  * @since 1.5.1
  171.  * @param string $form_url form action url
  172.  * @param string $name form name
  173.  * @param string $title form title
  174.  * @param string $button form button name
  175.  * @param array $defdata values of form fields
  176.  */
  177. function abook_create_form($form_url,$name,$title,$button,$defdata=array()) {
  178.     global $oTemplate;
  179.  
  180.     echo addForm($form_url'post''f_add');
  181.  
  182.     if ($button == _("Update address")) {
  183.         $edit true;
  184.         $backends NULL;
  185.     else {
  186.         $edit false;
  187.         $backends getWritableBackends();
  188.     }
  189.     
  190.     $fields array (
  191.                         'nickname'  => 'NickName',
  192.                         'firstname' => 'FirstName',
  193.                         'lastname'  => 'LastName',
  194.                         'email'     => 'Email',
  195.                         'label'     => 'Info',
  196.                     );
  197.     $values array();
  198.     foreach ($fields as $sqm=>$template{
  199.         $values[$template= isset($defdata[$sqm]$defdata[$sqm'';
  200.     }
  201.     
  202.     $oTemplate->assign('writable_backends'$backends);
  203.     $oTemplate->assign('values'$values);
  204.     $oTemplate->assign('edit'$edit);
  205.     
  206.     $oTemplate->display('addrbook_addedit.tpl');
  207. }
  208.  
  209.  
  210. /**
  211.  *   Had to move this function outside of the Addressbook Class
  212.  *   PHP 4.0.4 Seemed to be having problems with inline functions.
  213.  *   Note: this can return now since we don't support 4.0.4 anymore.
  214.  */
  215. function addressbook_cmp($a,$b{
  216.  
  217.     if($a['backend'$b['backend']{
  218.         return 1;
  219.     else if($a['backend'$b['backend']{
  220.         return -1;
  221.     }
  222.  
  223.     return (strtolower($a['name']strtolower($b['name'])) : -1;
  224.  
  225. }
  226.  
  227. /**
  228.  * Retrieve a list of writable backends
  229.  * @since 1.5.2
  230.  */
  231. function getWritableBackends ({
  232.     global $abook;
  233.     
  234.     $write array();
  235.     $backends $abook->get_backend_list();
  236.     while (list($undef,$veach($backends)) {
  237.         if ($v->writeable{
  238.             $write[$v->bnum]=$v->sname;
  239.         }
  240.     }
  241.  
  242.     return $write;
  243. }
  244.  
  245. /**
  246.  * Sort array by the key "name"
  247.  */
  248. function alistcmp($a,$b{
  249.     $abook_sort_order=get_abook_sort();
  250.  
  251.     switch ($abook_sort_order{
  252.     case 0:
  253.     case 1:
  254.       $abook_sort='nickname';
  255.       break;
  256.     case 4:
  257.     case 5:
  258.       $abook_sort='email';
  259.       break;
  260.     case 6:
  261.     case 7:
  262.       $abook_sort='label';
  263.       break;
  264.     case 2:
  265.     case 3:
  266.     case 8:
  267.     default:
  268.       $abook_sort='name';
  269.     }
  270.  
  271.     if ($a['backend'$b['backend']{
  272.         return 1;
  273.     else {
  274.         if ($a['backend'$b['backend']{
  275.             return -1;
  276.         }
  277.     }
  278.  
  279.     if( (($abook_sort_order+22== 1{
  280.       return (strtolower($a[$abook_sort]strtolower($b[$abook_sort])) : -1;
  281.     else {
  282.       return (strtolower($a[$abook_sort]strtolower($b[$abook_sort])) : -1;
  283.     }
  284. }
  285.  
  286. /**
  287.  * Address book sorting options
  288.  *
  289.  * returns address book sorting order
  290.  * @return integer book sorting options order
  291.  */
  292. function get_abook_sort({
  293.     global $data_dir$username;
  294.  
  295.     /* get sorting order */
  296.     if(sqgetGlobalVar('abook_sort_order'$tempSQ_GET)) {
  297.       $abook_sort_order = (int) $temp;
  298.  
  299.       if ($abook_sort_order or $abook_sort_order 8)
  300.         $abook_sort_order=8;
  301.  
  302.       setPref($data_dir$username'abook_sort_order'$abook_sort_order);
  303.     else {
  304.       /* get previous sorting options. default to unsorted */
  305.       $abook_sort_order getPref($data_dir$username'abook_sort_order'8);
  306.     }
  307.  
  308.     return $abook_sort_order;
  309. }
  310.  
  311. /**
  312.  * This function shows the address book sort button.
  313.  *
  314.  * @param integer $abook_sort_order current sort value
  315.  * @param string $alt_tag alt tag value (string visible to text only browsers)
  316.  * @param integer $Down sort value when list is sorted ascending
  317.  * @param integer $Up sort value when list is sorted descending
  318.  * @return string html code with sorting images and urls
  319.  */
  320. function show_abook_sort_button($abook_sort_order$alt_tag$Down$Up {
  321.     global $form_url$icon_theme_path;
  322.  
  323.      /* Figure out which image we want to use. */
  324.     if ($abook_sort_order != $Up && $abook_sort_order != $Down{
  325.         $img 'sort_none.png';
  326.         $text_icon '&#9723;'// U+25FB WHITE MEDIUM SQUARE
  327.         $which $Up;
  328.     elseif ($abook_sort_order == $Up{
  329.         $img 'up_pointer.png';
  330.         $text_icon '&#8679;'// U+21E7 UPWARDS WHITE ARROW
  331.         $which $Down;
  332.     else {
  333.         $img 'down_pointer.png';
  334.         $text_icon '&#8681;'// U+21E9 DOWNWARDS WHITE ARROW
  335.         $which 8;
  336.     }
  337.  
  338.     /* Now that we have everything figured out, show the actual button. */
  339.     return '&nbsp;<a href="' $form_url .'?abook_sort_order=' $which .
  340.            '" style="text-decoration:none" title="'.$alt_tag.'">' .
  341.            getIcon($icon_theme_path$img$text_icon$alt_tag.
  342.            '</a>';
  343. }
  344.  
  345.  
  346. /**
  347.  * This is the main address book class that connect all the
  348.  * backends and provide services to the functions above.
  349.  * @package squirrelmail
  350.  * @subpackage addressbook
  351.  */
  352. class AddressBook {
  353.     /**
  354.      * Enabled address book backends
  355.      * @var array 
  356.      */
  357.     var $backends    = array();
  358.     /**
  359.      * Number of enabled backends
  360.      * @var integer 
  361.      */
  362.     var $numbackends = 0;
  363.     /**
  364.      * Error messages
  365.      * @var string 
  366.      */
  367.     var $error       = '';
  368.     /**
  369.      * id of backend with personal address book
  370.      * @var integer 
  371.      */
  372.     var $localbackend = 0;
  373.     /**
  374.      * Name of backend with personal address book
  375.      * @var string 
  376.      */
  377.     var $localbackendname = '';
  378.     /**
  379.      * Controls use of 'extra' field
  380.      *
  381.      * Extra field can be used to add link to form, which allows
  382.      * to modify all fields supported by backend. This is the only field
  383.      * that is not sanitized with htmlspecialchars. Backends MUST make
  384.      * sure that field data is sanitized and displayed correctly inside
  385.      * table cell. Use of html formating in other address book fields is
  386.      * not allowed. Backends that don't return 'extra' row in address book
  387.      * data should not modify this object property.
  388.      * @var boolean 
  389.      * @since 1.5.1
  390.      */
  391.     var $add_extra_field = false;
  392.  
  393.     /**
  394.      * Constructor function.
  395.      */
  396.     function AddressBook({
  397.         $this->localbackendname = _("Personal address book");
  398.     }
  399.  
  400.     /**
  401.      * Return an array of backends of a given type,
  402.      * or all backends if no type is specified.
  403.      * @param string $type backend type
  404.      * @return array list of backends
  405.      */
  406.     function get_backend_list($type ''{
  407.         $ret array();
  408.         for ($i $i <= $this->numbackends $i++{
  409.             if (empty($type|| $type == $this->backends[$i]->btype{
  410.                 $ret[&$this->backends[$i];
  411.             }
  412.         }
  413.         return $ret;
  414.     }
  415.  
  416.  
  417.     /* ========================== Public ======================== */
  418.  
  419.     /**
  420.      * Add a new backend.
  421.      *
  422.      * @param string $backend backend name (without the abook_ prefix)
  423.      * @param mixed optional variable that is passed to the backend constructor.
  424.      *  See each of the backend classes for valid parameters
  425.      * @return integer number of backends
  426.      */
  427.     function add_backend($backend$param ''{
  428.         static $backend_classes;
  429.         if (!isset($backend_classes)) {
  430.             $backend_classes array();
  431.         }
  432.         if (!isset($backend_classes[$backend])) {
  433.             /**
  434.               * Support backend provided by plugins. Plugin function must
  435.               * return an associative array with as key the backend name ($backend)
  436.               * and as value the file including the path containing the backend class.
  437.               * i.e.: $aBackend = array('backend_template' => SM_PATH . 'plugins/abook_backend_template/functions.php')
  438.               *
  439.               * NB: Because the backend files are included from within this function they DO NOT have access to
  440.               * vars in the global scope. This function is the global scope for the included backend !!!
  441.               */
  442.             $aBackend do_hook('abook_add_class');
  443.             if (isset($aBackend&& is_array($aBackend&& isset($aBackend[$backend])) {
  444.                 require_once($aBackend[$backend]);
  445.             else {
  446.                 require_once(SM_PATH 'functions/abook_'.$backend.'.php');
  447.             }
  448.             $backend_classes[$backendtrue;
  449.         }
  450.         $backend_name 'abook_' $backend;
  451.         $newback new $backend_name($param);
  452.         //eval('$newback = new ' . $backend_name . '($param);');
  453.         if(!empty($newback->error)) {
  454.             $this->error = $newback->error;
  455.             return false;
  456.         }
  457.  
  458.         $this->numbackends++;
  459.  
  460.         $newback->bnum $this->numbackends;
  461.         $this->backends[$this->numbackends$newback;
  462.  
  463.         /* Store ID of first local backend added */
  464.         if ($this->localbackend == && $newback->btype == 'local'{
  465.             $this->localbackend = $this->numbackends;
  466.             $this->localbackendname = $newback->sname;
  467.         }
  468.  
  469.         return $this->numbackends;
  470.     }
  471.  
  472.  
  473.     /**
  474.      * create string with name and email address
  475.      *
  476.      * This function takes a $row array as returned by the addressbook
  477.      * search and returns an e-mail address with the full name or
  478.      * nickname optionally prepended.
  479.      * @param array $row address book entry
  480.      * @return string email address with real name prepended
  481.      */
  482.     function full_address($row{
  483.         global $addrsrch_fullname$data_dir$username;
  484.         $prefix getPref($data_dir$username'addrsrch_fullname');
  485.         if (($prefix != "" || (isset($addrsrch_fullname&&
  486.             $prefix == $addrsrch_fullname)) && $prefix != 'noprefix'{
  487.             $name ($prefix == 'nickname' $row['nickname'$row['name']);
  488.             return $name ' <' trim($row['email']'>';
  489.         else {
  490.             return trim($row['email']);
  491.         }
  492.     }
  493.  
  494.     /**
  495.      * Search for entries in address books
  496.      *
  497.      * Return a list of addresses matching expression in
  498.      * all backends of a given type.
  499.      * @param string $expression search expression
  500.      * @param integer $bnum backend number. default to search in all backends
  501.      * @return array search results
  502.      */
  503.     function search($expression$bnum = -1{
  504.         $ret array();
  505.         $this->error = '';
  506.  
  507.         /* Search all backends */
  508.         if ($bnum == -1{
  509.             $sel $this->get_backend_list('');
  510.             $failed 0;
  511.             for ($i $i sizeof($sel$i++{
  512.                 $backend &$sel[$i];
  513.                 $backend->error '';
  514.      &nb