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

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