Source for file abook_local_file.php

Documentation is available at abook_local_file.php

  1. <?php
  2.  
  3. /**
  4.  * abook_local_file.php
  5.  *
  6.  * @copyright &copy; 1999-2006 The SquirrelMail Project Team
  7.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8.  * @version $Id: abook_local_file.php,v 1.22.2.14 2006/10/07 11:58:42 tokul Exp $
  9.  * @package squirrelmail
  10.  * @subpackage addressbook
  11.  */
  12.  
  13. /**
  14.  * Backend for address book as a pipe separated file
  15.  *
  16.  * Stores the address book in a local file
  17.  *
  18.  * An array with the following elements must be passed to
  19.  * the class constructor (elements marked ? are optional):
  20.  *<pre>
  21.  *   filename  => path to addressbook file
  22.  * ? create    => if true: file is created if it does not exist.
  23.  * ? umask     => umask set before opening file.
  24.  * ? name      => name of address book.
  25.  * ? detect_writeable => detect address book access permissions by
  26.  *                checking file permissions.
  27.  * ? writeable => allow writing into address book. Used only when
  28.  *                detect_writeable is set to false.
  29.  * ? listing   => enable/disable listing
  30.  * ? line_length => allowed address book record size
  31.  *</pre>
  32.  * NOTE. This class should not be used directly. Use the
  33.  *       "AddressBook" class instead.
  34.  * @package squirrelmail
  35.  */
  36.     /**
  37.      * Backend type
  38.      * @var string 
  39.      */
  40.     var $btype = 'local';
  41.     /**
  42.      * Backend name
  43.      * @var string 
  44.      */
  45.     var $bname = 'local_file';
  46.  
  47.     /**
  48.      * File used to store data
  49.      * @var string 
  50.      */
  51.     var $filename = '';
  52.     /**
  53.      * File handle
  54.      * @var object 
  55.      */
  56.     var $filehandle = 0;
  57.     /**
  58.      * Create file, if it not present
  59.      * @var bool 
  60.      */
  61.     var $create = false;
  62.     /**
  63.      * Detect, if address book is writeable by checking file permisions
  64.      * @var bool 
  65.      */
  66.     var $detect_writeable   = true;
  67.     /**
  68.      * Control write access to address book
  69.      *
  70.      * Option does not have any effect, if 'detect_writeable' is 'true'
  71.      * @var bool 
  72.      */
  73.     var $writeable = false;
  74.     /**
  75.      * controls listing of address book
  76.      * @var bool 
  77.      * @since 1.5.1 and 1.4.9
  78.      */
  79.     var $listing = true;
  80.     /**
  81.      * Umask of the file
  82.      * @var string 
  83.      */
  84.     var $umask;
  85.     /**
  86.      * Sets max entry size (number of bytes used for all address book fields
  87.      * (including escapes) + 4 delimiters + 1 linefeed)
  88.      * @var integer 
  89.      * @since 1.5.2 and 1.4.9
  90.      */
  91.     var $line_length = 2048;
  92.  
  93.     /* ========================== Private ======================= */
  94.  
  95.     /**
  96.      * Constructor
  97.      * @param array $param backend options
  98.      * @return bool 
  99.      */
  100.     function abook_local_file($param{
  101.         $this->sname = _("Personal address book");
  102.         $this->umask = Umask();
  103.  
  104.         if(is_array($param)) {
  105.             if(empty($param['filename'])) {
  106.                 return $this->set_error('Invalid parameters');
  107.             }
  108.             if(!is_string($param['filename'])) {
  109.                 return $this->set_error($param['filename'': '.
  110.                      _("Not a file name"));
  111.             }
  112.  
  113.             $this->filename = $param['filename'];
  114.  
  115.             if(isset($param['create'])) {
  116.                 $this->create = $param['create'];
  117.             }
  118.             if(isset($param['umask'])) {
  119.                 $this->umask = $param['umask'];
  120.             }
  121.             if(isset($param['name'])) {
  122.                 $this->sname = $param['name'];
  123.             }
  124.             if(isset($param['detect_writeable'])) {
  125.                 $this->detect_writeable = $param['detect_writeable'];
  126.             }
  127.             if(!empty($param['writeable'])) {
  128.                 $this->writeable = $param['writeable'];
  129.             }
  130.             if(isset($param['listing'])) {
  131.                 $this->listing = $param['listing'];
  132.             }
  133.             if(isset($param['line_length']&& empty($param['line_length'])) {
  134.                 $this->line_length = (int) $param['line_length'];
  135.             }
  136.  
  137.             $this->open(true);
  138.         else {
  139.             $this->set_error('Invalid argument to constructor');
  140.         }
  141.     }
  142.  
  143.     /**
  144.      * Open the addressbook file and store the file pointer.
  145.      * Use $file as the file to open, or the class' own
  146.      * filename property. If $param is empty and file is
  147.      * open, do nothing.
  148.      * @param bool $new is file already opened
  149.      * @return bool 
  150.      */
  151.     function open($new false{
  152.         $this->error = '';
  153.         $file   $this->filename;
  154.         $create $this->create;
  155.         $fopenmode (($this->writeable && is_writable($file)) 'a+' 'r');
  156.  
  157.         /* Return true is file is open and $new is unset */
  158.         if($this->filehandle && !$new{
  159.             return true;
  160.         }
  161.  
  162.         /* Check that new file exitsts */
  163.         if((!(file_exists($file&& is_readable($file))) && !$create{
  164.             return $this->set_error("$file_("No such file or directory"));
  165.         }
  166.  
  167.         /* Close old file, if any */
  168.         if($this->filehandle$this->close()}
  169.  
  170.         umask($this->umask);
  171.         if ($this->detect_writeable{
  172.             $fh @fopen($file,$fopenmode);
  173.             if ($fh{
  174.                 $this->filehandle = &$fh;
  175.                 $this->filename = $file;
  176.             else {
  177.                 return $this->set_error("$file_("Open failed"));
  178.             }
  179.         else {
  180.             /* Open file. First try to open for reading and writing,
  181.              * but fall back to read only. */
  182.             $fh @fopen($file'a+');
  183.             if($fh{
  184.                 $this->filehandle = &$fh;
  185.                 $this->filename   = $file;
  186.                 $this->writeable  = true;
  187.             else {
  188.                 $fh @fopen($file'r');
  189.                 if($fh{
  190.                     $this->filehandle = &$fh;
  191.                     $this->filename   = $file;
  192.                     $this->writeable  = false;
  193.                 else {
  194.                     return $this->set_error("$file_("Open failed"));
  195.                 }
  196.             }
  197.         }
  198.         return true;
  199.     }
  200.  
  201.     /** Close the file and forget the filehandle */
  202.     function close({
  203.         @fclose($this->filehandle);
  204.         $this->filehandle = 0;
  205.         $this->filename   = '';
  206.         $this->writable   false;
  207.     }
  208.  
  209.     /** Lock the datafile - try 20 times in 5 seconds */
  210.     function lock({
  211.         for($i $i 20 $i++{
  212.             if(flock($this->filehandle4))
  213.                 return true;
  214.             else
  215.                 usleep(250000);
  216.         }
  217.         return false;
  218.     }
  219.  
  220.     /** Unlock the datafile */
  221.     function unlock({
  222.         return flock($this->filehandle3);
  223.     }
  224.  
  225.     /**
  226.      * Overwrite the file with data from $rows
  227.      * NOTE! Previous locks are broken by this function
  228.      * @param array $rows new data
  229.      * @return bool 
  230.      */
  231.     function overwrite(&$rows{
  232.         $this->unlock();
  233.         $newfh @fopen($this->filename.'.tmp''w');
  234.  
  235.         if(!$newfh{
  236.             return $this->set_error($this->filename'.tmp:' _("Open failed"));
  237.         }
  238.  
  239.         for($i 0$cnt=sizeof($rows$i $cnt $i++{
  240.             if(is_array($rows[$i])) {
  241.                 for($j 0$cnt_part=count($rows[$i]$j $cnt_part $j++{
  242.                     $rows[$i][$j$this->quotevalue($rows[$i][$j]);
  243.                 }
  244.                 $tmpwrite sq_fwrite($newfhjoin('|'$rows[$i]"\n");
  245.                 if ($tmpwrite === FALSE{
  246.                     return $this->set_error($this->filename . '.tmp:' _("Write failed"));
  247.                 }
  248.             }
  249.         }
  250.  
  251.         fclose($newfh);
  252.         if (!@copy($this->filename . '.tmp' $this->filename)) {
  253.           return $this->set_error($this->filename . ':' _("Unable to update"));
  254.         }
  255.         @unlink($this->filename . '.tmp');
  256.         $this->unlock();
  257.         $this->open(true);
  258.         return true;
  259.     }
  260.  
  261.     /* ========================== Public ======================== */
  262.  
  263.     /**
  264.      * Search the file
  265.      * @param string $expr search expression
  266.      * @return array search results
  267.      */
  268.     function search($expr{
  269.  
  270.         /* To be replaced by advanded search expression parsing */
  271.         if(is_array($expr)) return}
  272.  
  273.         // don't allow wide search when listing is disabled.
  274.         if ($expr=='*' && $this->listing)
  275.             return array();
  276.  
  277.         /* Make regexp from glob'ed expression
  278.          * May want to quote other special characters like (, ), -, [, ], etc. */
  279.         $expr str_replace('?''.'$expr);
  280.         $expr str_replace('*''.*'$expr);
  281.  
  282.         $res array();
  283.         if(!$this->open()) {
  284.             return false;
  285.         }
  286.         @rewind($this->filehandle);
  287.  
  288.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  289.             if (count($row)<5{
  290.                 /** address book is corrupted. */
  291.                 global $color;
  292.                 error_box(_("Address book is corrupted. Required fields are missing."),$color);
  293.                 die('</body></html>');
  294.             else {
  295.                 $line join(' '$row);
  296.                 // errors on eregi call are suppressed in order to prevent display of regexp compilation errors
  297.                 if(@eregi($expr$line)) {
  298.                     array_push($resarray('nickname'  => $row[0],
  299.                                            'name'      => $row[1' ' $row[2],
  300.                                            'firstname' => $row[1],
  301.                                            'lastname'  => $row[2],
  302.                                            'email'     => $row[3],
  303.                                            'label'     => $row[4],
  304.                                            'backend'   => $this->bnum,
  305.                                            'source'    => &$this->sname));
  306.                 }
  307.             }
  308.         }
  309.         return $res;
  310.     }
  311.  
  312.     /**
  313.      * Lookup alias
  314.      * @param string $alias alias
  315.      * @return array search results
  316.      */
  317.     function lookup($alias{
  318.         if(empty($alias)) {
  319.             return array();
  320.         }
  321.  
  322.         $alias strtolower($alias);
  323.  
  324.         $this->open();
  325.         @rewind($this->filehandle);
  326.  
  327.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  328.             if (count($row)<5{
  329.                 /** address book is corrupted. */
  330.                 global $color;
  331.                 error_box(_("Address book is corrupted. Required fields are missing."),$color);
  332.                 die('</body></html>');
  333.             else {
  334.                 if(strtolower($row[0]== $alias{
  335.                     return array('nickname'  => $row[0],
  336.                                  'name'      => $row[1' ' $row[2],
  337.                                  'firstname' => $row[1],
  338.                                  'lastname'  => $row[2],
  339.                                  'email'     => $row[3],
  340.                                  'label'     => $row[4],
  341.                                  'backend'   => $this->bnum,
  342.                                  'source'    => &$this->sname);
  343.                 }
  344.             }
  345.         }
  346.  
  347.         return array();
  348.     }
  349.  
  350.     /**
  351.      * List all addresses
  352.      * @return array list of all addresses
  353.      */
  354.     function list_addr({
  355.         // check if listing is not disabled
  356.         if(isset($this->listing&& !$this->listing{
  357.             return array();
  358.         }
  359.  
  360.         $res array();
  361.         $this->open();
  362.         @rewind($this->filehandle);
  363.  
  364.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  365.             if (count($row)<5{
  366.                 /** address book is corrupted. */
  367.                 global $color;
  368.                 error_box(_("Address book is corrupted. Required fields are missing."),$color);
  369.                 die('</body></html>');
  370.             else {
  371.                 array_push($resarray('nickname'  => $row[0],
  372.                                        'name'      => $row[1' ' $row[2],
  373.                                        'firstname' => $row[1],
  374.                                        'lastname'  => $row[2],
  375.                                        'email'     => $row[3],
  376.                                        'label'     => $row[4],
  377.                                        'backend'   => $this->bnum,
  378.                                        'source'    => &$this->sname));
  379.             }
  380.         }
  381.         return $res;
  382.     }
  383.  
  384.     /**
  385.      * Add address
  386.      * @param array $userdata new data
  387.      * @return bool 
  388.      */
  389.     function add($userdata{
  390.         if(!$this->writeable{
  391.             return $this->set_error(_("Address book is read-only"));
  392.         }
  393.         /* See if user exists already */
  394.         $ret $this->lookup($userdata['nickname']);
  395.         if(!empty($ret)) {
  396.             // i18n: don't use html formating in translation
  397.             return $this->set_error(sprintf(_("User \"%s\" already exists")$ret['nickname']));
  398.         }
  399.  
  400.         /* Here is the data to write */
  401.         $data $this->quotevalue($userdata['nickname']'|' .
  402.                 $this->quotevalue($userdata['firstname']'|' .
  403.                 $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) '|' .
  404.                 $this->quotevalue($userdata['email']'|' .
  405.                 $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
  406.  
  407.         /* Strip linefeeds */
  408.         $data ereg_replace("[\r\n]"' '$data);
  409.  
  410.         /**
  411.          * Make sure that entry fits into allocated record space.
  412.          * One byte is reserved for linefeed
  413.          */
  414.         if (strlen($data>= $this->line_length{
  415.             return $this->set_error(_("Address book entry is too big"));
  416.         }
  417.  
  418.         /* Add linefeed at end */
  419.         $data $data "\n";
  420.  
  421.         /* Reopen file, just to be sure */
  422.         $this->open(true);
  423.         if(!$this->writeable{
  424.             return $this->set_error(_("Address book is read-only"));
  425.         }
  426.  
  427.         /* Lock the file */
  428.         if(!$this->lock()) {
  429.             return $this->set_error(_("Could not lock datafile"));
  430.         }
  431.  
  432.         /* Write */
  433.         $r sq_fwrite($this->filehandle$data);
  434.  
  435.         /* Unlock file */
  436.         $this->unlock();
  437.  
  438.         /* Test write result */
  439.         if($r === FALSE{
  440.             /* Fail */
  441.             $this->set_error(_("Write to address book failed"));
  442.             return FALSE;
  443.         }
  444.  
  445.         return TRUE;
  446.     }
  447.  
  448.     /**
  449.      * Delete address
  450.      * @param string $alias alias that has to be deleted
  451.      * @return bool 
  452.      */
  453.     function remove($alias{
  454.         if(!$this->writeable{
  455.             return $this->set_error(_("Address book is read-only"));
  456.         }
  457.  
  458.         /* Lock the file to make sure we're the only process working
  459.          * on it. */
  460.         if(!$this->lock()) {
  461.             return $this->set_error(_("Could not lock datafile"));
  462.         }
  463.  
  464.         /* Read file into memory, ignoring nicknames to delete */
  465.         @rewind($this->filehandle);
  466.         $i 0;
  467.         $rows array();
  468.         while($row @fgetcsv($this->filehandle$this->line_length'|')) {
  469.             if(!in_array($row[0]$alias)) {
  470.                 $rows[$i++$row;
  471.             }
  472.         }
  473.  
  474.         /* Write data back */
  475.         if(!$this->overwrite($rows)) {
  476.             $this->unlock();
  477.             return false;
  478.         }
  479.  
  480.         $this->unlock();
  481.         return true;
  482.     }
  483.  
  484.     /**
  485.      * Modify address
  486.      * @param string $alias modified alias
  487.      * @param array $userdata new data
  488.      * @return bool true, if operation successful
  489.      */
  490.     function modify($alias$userdata{
  491.         if(!$this->writeable{
  492.             return $this->set_error(_("Address book is read-only"));
  493.         }
  494.  
  495.         /* See if user exists */
  496.         $ret $this->lookup($alias);
  497.         if(empty($ret)) {
  498.             // i18n: don't use html formating in translation
  499.             return $this->set_error(sprintf(_("User \"%s\" does not exist")$alias));
  500.         }
  501.  
  502.         /* If the alias changed, see if the new alias exists */
  503.         if (strtolower($alias!= strtolower($userdata['nickname'])) {
  504.             $ret $this->lookup($userdata['nickname']);
  505.             if (!empty($ret)) {
  506.                 return $this->set_error(sprintf(_("User \"%s\" already exists")$userdata['nickname']));
  507.             }
  508.         }
  509.  
  510.         /* calculate userdata size */
  511.         $data $this->quotevalue($userdata['nickname']'|'
  512.             . $this->quotevalue($userdata['firstname']'|'
  513.             . $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) '|'
  514.             . $this->quotevalue($userdata['email']'|'
  515.             . $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
  516.         /* make sure that it fits into allocated space */
  517.         if (strlen($data>= $this->line_length{
  518.             return $this->set_error(_("Address book entry is too big"));
  519.         }
  520.  
  521.         /* Lock the file to make sure we're the only process working
  522.          * on it. */
  523.         if(!$this->lock()) {
  524.             return $this->set_error(_("Could not lock datafile"));
  525.         }
  526.  
  527.         /* Read file into memory, modifying the data for the
  528.          * user identified by $alias */
  529.         $this->open(true);
  530.         @rewind($this->filehandle);
  531.         $i 0;
  532.         $rows array();
  533.         while($row @fgetcsv($this->filehandle$this->line_length'|')) {
  534.             if(strtolower($row[0]!= strtolower($alias)) {
  535.                 $rows[$i++$row;
  536.             else {
  537.                 $rows[$i++array(=> $userdata['nickname'],
  538.                                     => $userdata['firstname'],
  539.                                     => (!empty($userdata['lastname'])?$userdata['lastname']:''),
  540.                                     => $userdata['email'],
  541.                                     => (!empty($userdata['label'])?$userdata['label']:''));
  542.             }
  543.         }
  544.  
  545.         /* Write data back */
  546.         if(!$this->overwrite($rows)) {
  547.             $this->unlock();
  548.             return false;
  549.         }
  550.  
  551.         $this->unlock();
  552.         return true;
  553.     }
  554.  
  555.     /**
  556.      * Function for quoting values before saving
  557.      * @param string $value string that has to be quoted
  558.      * @param string quoted string
  559.      */
  560.     function quotevalue($value{
  561.         /* Quote the field if it contains | or ". Double quotes need to
  562.          * be replaced with "" */
  563.         if(ereg("[|\"]"$value)) {
  564.             $value '"' str_replace('"''""'$value'"';
  565.         }
  566.         return $value;
  567.     }
  568.  
  569. }

Documentation generated on Sat, 07 Oct 2006 16:29:40 +0300 by phpDocumentor 1.3.0RC6