Source for file abook_database.php

Documentation is available at abook_database.php

  1. <?php
  2.  
  3. /**
  4.  * abook_database.php
  5.  *
  6.  * @copyright 1999-2014 The SquirrelMail Project Team
  7.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8.  * @version $Id: abook_database.php 14466 2014-06-16 07:27:02Z pdontthink $
  9.  * @package squirrelmail
  10.  * @subpackage addressbook
  11.  */
  12.  
  13. /** Needs the DB functions */
  14. if (!include_once('DB.php')) {
  15.     // same error also in db_prefs.php
  16.     require_once(SM_PATH 'functions/display_messages.php');
  17.     $error  _("Could not include PEAR database functions required for the database backend.""<br />\n";
  18.     $error .= sprintf(_("Is PEAR installed, and is the include path set correctly to find %s?"),
  19.                         '<tt>DB.php</tt>'"<br />\n";
  20.     $error .= _("Please contact your system administrator and report this error.");
  21.     error_box($error$color);
  22.     exit;
  23. }
  24.  
  25. /**
  26.  * Address book in a database backend
  27.  *
  28.  * Backend for personal/shared address book stored in a database,
  29.  * accessed using the DB-classes in PEAR.
  30.  *
  31.  * IMPORTANT:  The PEAR modules must be in the include path
  32.  * for this class to work.
  33.  *
  34.  * An array with the following elements must be passed to
  35.  * the class constructor (elements marked ? are optional):
  36.  * <pre>
  37.  *   dsn       => database DNS (see PEAR for syntax)
  38.  *   table     => table to store addresses in (must exist)
  39.  *   owner     => current user (owner of address data)
  40.  * ? name      => name of address book
  41.  * ? writeable => set writeable flag (true/false)
  42.  * ? listing   => enable/disable listing
  43.  * </pre>
  44.  * The table used should have the following columns:
  45.  * owner, nickname, firstname, lastname, email, label
  46.  * The pair (owner,nickname) should be unique (primary key).
  47.  *
  48.  *  NOTE. This class should not be used directly. Use the
  49.  *        "AddressBook" class instead.
  50.  * @package squirrelmail
  51.  * @subpackage addressbook
  52.  */
  53.     /**
  54.      * Backend type
  55.      * @var string 
  56.      */
  57.     var $btype = 'local';
  58.     /**
  59.      * Backend name
  60.      * @var string 
  61.      */
  62.     var $bname = 'database';
  63.  
  64.     /**
  65.      * Data Source Name (connection description)
  66.      * @var string 
  67.      */
  68.     var $dsn       = '';
  69.     /**
  70.      * Table that stores addresses
  71.      * @var string 
  72.      */
  73.     var $table     = '';
  74.     /**
  75.      * Owner name
  76.      *
  77.      * Limits list of database entries visible to end user
  78.      * @var string 
  79.      */
  80.     var $owner     = '';
  81.     /**
  82.      * Database Handle
  83.      * @var resource 
  84.      */
  85.     var $dbh       = false;
  86.     /**
  87.      * Enable/disable writing into address book
  88.      * @var bool 
  89.      */
  90.     var $writeable = true;
  91.     /**
  92.      * Enable/disable address book listing
  93.      * @var bool 
  94.      */
  95.     var $listing = true;
  96.  
  97.     /* ========================== Private ======================= */
  98.  
  99.     /**
  100.      * Constructor
  101.      * @param array $param address book backend options
  102.      */
  103.     function abook_database($param{
  104.         $this->sname = _("Personal address book");
  105.  
  106.         if (is_array($param)) {
  107.             if (empty($param['dsn']||
  108.                 empty($param['table']||
  109.                 empty($param['owner'])) {
  110.                 return $this->set_error('Invalid parameters');
  111.             }
  112.  
  113.             $this->dsn   = $param['dsn'];
  114.             $this->table = $param['table'];
  115.             $this->owner = $param['owner'];
  116.  
  117.             if (!empty($param['name'])) {
  118.                $this->sname = $param['name'];
  119.             }
  120.  
  121.             if (isset($param['writeable'])) {
  122.                $this->writeable = $param['writeable'];
  123.             }
  124.  
  125.             if (isset($param['listing'])) {
  126.                $this->listing = $param['listing'];
  127.             }
  128.  
  129.             $this->open(true);
  130.         }
  131.         else {
  132.             return $this->set_error('Invalid argument to constructor');
  133.         }
  134.     }
  135.  
  136.  
  137.     /**
  138.      * Open the database.
  139.      * @param bool $new new connection if it is true
  140.      * @return bool 
  141.      */
  142.     function open($new false{
  143.         $this->error = '';
  144.  
  145.         /* Return true is file is open and $new is unset */
  146.         if ($this->dbh && !$new{
  147.             return true;
  148.         }
  149.  
  150.         /* Close old file, if any */
  151.         if ($this->dbh{
  152.             $this->close();
  153.         }
  154.  
  155.         $dbh DB::connect($this->dsntrue);
  156.  
  157.         if (DB::isError($dbh)) {
  158.             return $this->set_error(sprintf(_("Database error: %s"),
  159.                                             DB::errorMessage($dbh)));
  160.         }
  161.  
  162.         $this->dbh = $dbh;
  163.  
  164.         /**
  165.          * field names are lowercased.
  166.          * We use unquoted identifiers and they use upper case in Oracle
  167.          */
  168.         $this->dbh->setOption('portability'DB_PORTABILITY_LOWERCASE);
  169.  
  170.         return true;
  171.     }
  172.  
  173.     /**
  174.      * Close the file and forget the filehandle
  175.      */
  176.     function close({
  177.         $this->dbh->disconnect();
  178.         $this->dbh = false;
  179.     }
  180.  
  181.     /**
  182.      * Determine internal database field name given one of
  183.      * the SquirrelMail SM_ABOOK_FIELD_* constants
  184.      *
  185.      * @param integer $field The SM_ABOOK_FIELD_* contant to look up
  186.      *
  187.      * @return string The desired field name, or the string "ERROR"
  188.      *                 if the $field is not understood (the caller
  189.      *                 is responsible for handing errors)
  190.      *
  191.      */
  192.     function get_field_name($field{
  193.         switch ($field{
  194.             case SM_ABOOK_FIELD_NICKNAME:
  195.                 return 'nickname';
  196.             case SM_ABOOK_FIELD_FIRSTNAME:
  197.                 return 'firstname';
  198.             case SM_ABOOK_FIELD_LASTNAME:
  199.                 return 'lastname';
  200.             case SM_ABOOK_FIELD_EMAIL:
  201.                 return 'email';
  202.             case SM_ABOOK_FIELD_LABEL:
  203.                 return 'label';
  204.             default:
  205.                 return 'ERROR';
  206.         }
  207.     }
  208.  
  209.     /* ========================== Public ======================== */
  210.  
  211.     /**
  212.      * Search the database
  213.      * @param string $expr search expression
  214.      * @return array search results
  215.      */
  216.     function search($expr{
  217.         $ret array();
  218.         if(!$this->open()) {
  219.             return false;
  220.         }
  221.  
  222.         /* To be replaced by advanded search expression parsing */
  223.         if (is_array($expr)) {
  224.             return;
  225.         }
  226.  
  227.         // don't allow wide search when listing is disabled.
  228.         if ($expr=='*' && $this->listing{
  229.             return array();
  230.         }
  231.  
  232.         /* lowercase expression in order to make it case insensitive */
  233.         $expr strtolower($expr);
  234.  
  235.         /* escape SQL wildcards */
  236.         $expr str_replace('_''\\_'$expr);
  237.         $expr str_replace('%''\\%'$expr);
  238.  
  239.         /* Convert wildcards to SQL syntax  */
  240.         $expr str_replace('?''_'$expr);
  241.         $expr str_replace('*''%'$expr);
  242.         $expr $this->dbh->quoteString($expr);
  243.         $expr "%$expr%";
  244.  
  245.         /* create escape expression */
  246.         $escape 'ESCAPE \'' $this->dbh->quoteString('\\''\'';
  247.  
  248.         $query sprintf("SELECT * FROM %s WHERE owner='%s' AND " .
  249.                          "(LOWER(firstname) LIKE '%s' %s " .
  250.                          "OR LOWER(lastname) LIKE '%s' %s " .
  251.                          "OR LOWER(email) LIKE '%s' %s " .
  252.                          "OR LOWER(nickname) LIKE '%s' %s)",
  253.                          $this->table$this->owner$expr$escape$expr$escape,
  254.                                                      $expr$escape$expr$escape);
  255.         $res $this->dbh->query($query);
  256.  
  257.         if (DB::isError($res)) {
  258.             return $this->set_error(sprintf(_("Database error: %s"),
  259.                                             DB::errorMessage($res)));
  260.         }
  261.  
  262.         while ($row $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  263.             array_push($retarray('nickname'  => $row['nickname'],
  264.                                    'name'      => "$row[firstname] $row[lastname]",
  265.                                    'firstname' => $row['firstname'],
  266.                                    'lastname'  => $row['lastname'],
  267.                                    'email'     => $row['email'],
  268.                                    'label'     => $row['label'],
  269.                                    'backend'   => $this->bnum,
  270.                                    'source'    => &$this->sname));
  271.         }
  272.         return $ret;
  273.     }
  274.  
  275.     /**
  276.      * Lookup by the indicated field
  277.      *
  278.      * @param string  $value Value to look up
  279.      * @param integer $field The field to look in, should be one
  280.      *                        of the SM_ABOOK_FIELD_* constants
  281.      *                        defined in functions/constants.php
  282.      *                        (OPTIONAL; defaults to nickname field)
  283.      *                        NOTE: uniqueness is only guaranteed
  284.      *                        when the nickname field is used here;
  285.      *                        otherwise, the first matching address
  286.      *                        is returned.
  287.      *
  288.      * @return array search results
  289.      *
  290.      */
  291.     function lookup($value$field=SM_ABOOK_FIELD_NICKNAME{
  292.         if (empty($value)) {
  293.             return array();
  294.         }
  295.  
  296.         $value strtolower($value);
  297.  
  298.         if (!$this->open()) {
  299.             return false;
  300.         }
  301.  
  302.         $query sprintf("SELECT * FROM %s WHERE owner = '%s' AND LOWER(%s) = '%s'",
  303.                          $this->table$this->owner$this->get_field_name($field)
  304.                          $this->dbh->quoteString($value));
  305.  
  306.         $res $this->dbh->query($query);
  307.  
  308.         if (DB::isError($res)) {
  309.             return $this->set_error(sprintf(_("Database error: %s"),
  310.                                             DB::errorMessage($res)));
  311.         }
  312.  
  313.         if ($row $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  314.             return array('nickname'  => $row['nickname'],
  315.                          'name'      => "$row[firstname] $row[lastname]",
  316.                          'firstname' => $row['firstname'],
  317.                          'lastname'  => $row['lastname'],
  318.                          'email'     => $row['email'],
  319.                          'label'     => $row['label'],
  320.                          'backend'   => $this->bnum,
  321.                          'source'    => &$this->sname);
  322.         }
  323.         return array();
  324.     }
  325.  
  326.     /**
  327.      * List all addresses
  328.      * @return array search results
  329.      */
  330.     function list_addr({
  331.         $ret array();
  332.         if (!$this->open()) {
  333.             return false;
  334.         }
  335.  
  336.         if(isset($this->listing&& !$this->listing{
  337.             return array();
  338.         }
  339.  
  340.  
  341.         $query sprintf("SELECT * FROM %s WHERE owner='%s'",
  342.                          $this->table$this->owner);
  343.  
  344.         $res $this->dbh->query($query);
  345.  
  346.         if (DB::isError($res)) {
  347.             return $this->set_error(sprintf(_("Database error: %s"),
  348.                                             DB::errorMessage($res)));
  349.         }
  350.  
  351.         while ($row $res->fetchRow(DB_FETCHMODE_ASSOC)) {
  352.             array_push($retarray('nickname'  => $row['nickname'],
  353.                                    'name'      => "$row[firstname] $row[lastname]",
  354.                                    'firstname' => $row['firstname'],
  355.                                    'lastname'  => $row['lastname'],
  356.                                    'email'     => $row['email'],
  357.                                    'label'     => $row['label'],
  358.                                    'backend'   => $this->bnum,
  359.                                    'source'    => &$this->sname));
  360.         }
  361.         return $ret;
  362.     }
  363.  
  364.     /**
  365.      * Add address
  366.      * @param array $userdata added data
  367.      * @return bool 
  368.      */
  369.     function add($userdata{
  370.         if (!$this->writeable{
  371.             return $this->set_error(_("Address book is read-only"));
  372.         }
  373.  
  374.         if (!$this->open()) {
  375.             return false;
  376.         }
  377.  
  378.         // NB: if you want to check for some unwanted characters
  379.         //     or other problems, do so here like this:
  380.         // TODO: Should pull all validation code out into a separate function
  381.         //if (strpos($userdata['nickname'], ' ')) {
  382.         //    return $this->set_error(_("Nickname contains illegal characters"));
  383.         //}
  384.  
  385.         /* See if user exist already */
  386.         $ret $this->lookup($userdata['nickname']);
  387.         if (!empty($ret)) {
  388.             return $this->set_error(sprintf(_("User \"%s\" already exists")$ret['nickname']));
  389.         }
  390.  
  391.         /* Create query */
  392.         $query sprintf("INSERT INTO %s (owner, nickname, firstname, " .
  393.                          "lastname, email, label) VALUES('%s','%s','%s'," .
  394.                          "'%s','%s','%s')",
  395.                          $this->table$this->owner,
  396.                          $this->dbh->quoteString($userdata['nickname']),
  397.                          $this->dbh->quoteString($userdata['firstname']),
  398.                          $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  399.                          $this->dbh->quoteString($userdata['email']),
  400.                          $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')) );
  401.  
  402.          /* Do the insert */
  403.          $r $this->dbh->simpleQuery($query);
  404.  
  405.          /* Check for errors */
  406.          if (DB::isError($r)) {
  407.              return $this->set_error(sprintf(_("Database error: %s"),
  408.                                              DB::errorMessage($r)));
  409.          }
  410.  
  411.          return true;
  412.     }
  413.  
  414.     /**
  415.      * Delete address
  416.      * @param string $alias alias that has to be deleted
  417.      * @return bool 
  418.      */
  419.     function remove($alias{
  420.         if (!$this->writeable{
  421.             return $this->set_error(_("Address book is read-only"));
  422.         }
  423.  
  424.         if (!$this->open()) {
  425.             return false;
  426.         }
  427.  
  428.         /* Create query */
  429.         $query sprintf("DELETE FROM %s WHERE owner='%s' AND (",
  430.                          $this->table$this->owner);
  431.  
  432.         $sepstr '';
  433.         while (list($undef$nicknameeach($alias)) {
  434.             $query .= sprintf("%s nickname='%s' "$sepstr,
  435.                               $this->dbh->quoteString($nickname));
  436.             $sepstr 'OR';
  437.         }
  438.         $query .= ')';
  439.  
  440.         /* Delete entry */
  441.         $r $this->dbh->simpleQuery($query);
  442.  
  443.         /* Check for errors */
  444.         if (DB::isError($r)) {
  445.             return $this->set_error(sprintf(_("Database error: %s"),
  446.                                             DB::errorMessage($r)));
  447.         }
  448.         return true;
  449.     }
  450.  
  451.     /**
  452.      * Modify address
  453.      * @param string $alias modified alias
  454.      * @param array $userdata new data
  455.      * @return bool 
  456.      */
  457.     function modify($alias$userdata{
  458.         if (!$this->writeable{
  459.             return $this->set_error(_("Address book is read-only"));
  460.         }
  461.  
  462.         if (!$this->open()) {
  463.             return false;
  464.         }
  465.  
  466.         // NB: if you want to check for some unwanted characters
  467.         //     or other problems, do so here like this:
  468.         // TODO: Should pull all validation code out into a separate function
  469.         //if (strpos($userdata['nickname'], ' ')) {
  470.         //    return $this->set_error(_("Nickname contains illegal characters"));
  471.         //}
  472.  
  473.          /* See if user exist */
  474.         $ret $this->lookup($alias);
  475.         if (empty($ret)) {
  476.             return $this->set_error(sprintf(_("User \"%s\" does not exist")$alias));
  477.         }
  478.  
  479.         /* make sure that new nickname is not used */
  480.         if (strtolower($alias!= strtolower($userdata['nickname'])) {
  481.             /* same check as in add() */
  482.             $ret $this->lookup($userdata['nickname']);
  483.             if (!empty($ret)) {
  484.                 $error sprintf(_("User '%s' already exist.")$ret['nickname']);
  485.                 return $this->set_error($error);
  486.             }
  487.         }
  488.  
  489.         /* Create query */
  490.         $query sprintf("UPDATE %s SET nickname='%s', firstname='%s', ".
  491.                          "lastname='%s', email='%s', label='%s' ".
  492.                          "WHERE owner='%s' AND nickname='%s'",
  493.                          $this->table,
  494.                          $this->dbh->quoteString($userdata['nickname']),
  495.                          $this->dbh->quoteString($userdata['firstname']),
  496.                          $this->dbh->quoteString((!empty($userdata['lastname'])?$userdata['lastname']:'')),
  497.                          $this->dbh->quoteString($userdata['email']),
  498.                          $this->dbh->quoteString((!empty($userdata['label'])?$userdata['label']:'')),
  499.                          $this->owner,
  500.                          $this->dbh->quoteString($alias) );
  501.  
  502.         /* Do the insert */
  503.         $r $this->dbh->simpleQuery($query);
  504.  
  505.         /* Check for errors */
  506.         if (DB::isError($r)) {
  507.             return $this->set_error(sprintf(_("Database error: %s"),
  508.                                             DB::errorMessage($r)));
  509.         }
  510.         return true;
  511.     }
  512. /* End of class abook_database */
  513.  
  514. // vim: et ts=4

Documentation generated on Wed, 30 Jul 2014 04:20:15 +0200 by phpDocumentor 1.4.3