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 1999-2014 The SquirrelMail Project Team
  7.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8.  * @version $Id: abook_local_file.php 14422 2014-01-01 20:59:43Z pdontthink $
  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.         @chmod($this->filename0600);
  257.         $this->unlock();
  258.         $this->open(true);
  259.         return true;
  260.     }
  261.  
  262.     /* ========================== Public ======================== */
  263.  
  264.     /**
  265.      * Search the file
  266.      * @param string $expr search expression
  267.      * @return array search results
  268.      */
  269.     function search($expr{
  270.  
  271.         /* To be replaced by advanded search expression parsing */
  272.         if(is_array($expr)) return}
  273.  
  274.         // don't allow wide search when listing is disabled.
  275.         if ($expr=='*' && $this->listing)
  276.             return array();
  277.  
  278.         // Make regexp from glob'ed expression
  279.         $expr preg_quote($expr);
  280.         $expr str_replace(array('\\?''\\*')array('.''.*')$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.                 // errors on preg_match call are suppressed in order to prevent display of regexp compilation errors
  296.                 if (@preg_match('/' $expr '/i'$row[0])    // nickname
  297.                  || @preg_match('/' $expr '/i'$row[1])    // firstname
  298.                  || @preg_match('/' $expr '/i'$row[2])    // lastname
  299.                  || @preg_match('/' $expr '/i'$row[3])) // email
  300.                     array_push($resarray('nickname'  => $row[0],
  301.                                            'name'      => $row[1' ' $row[2],
  302.                                            'firstname' => $row[1],
  303.                                            'lastname'  => $row[2],
  304.                                            'email'     => $row[3],
  305.                                            'label'     => $row[4],
  306.                                            'backend'   => $this->bnum,
  307.                                            'source'    => &$this->sname));
  308.                 }
  309.             }
  310.         }
  311.         return $res;
  312.     }
  313.  
  314.     /**
  315.      * Lookup by the indicated field
  316.      *
  317.      * @param string  $value Value to look up
  318.      * @param integer $field The field to look in, should be one
  319.      *                        of the SM_ABOOK_FIELD_* constants
  320.      *                        defined in functions/constants.php
  321.      *                        (OPTIONAL; defaults to nickname field)
  322.      *                        NOTE: uniqueness is only guaranteed
  323.      *                        when the nickname field is used here;
  324.      *                        otherwise, the first matching address
  325.      *                        is returned.
  326.      *
  327.      * @return array search results
  328.      *
  329.      */
  330.     function lookup($value$field=SM_ABOOK_FIELD_NICKNAME{
  331.         if(empty($value)) {
  332.             return array();
  333.         }
  334.  
  335.         $value strtolower($value);
  336.  
  337.         $this->open();
  338.         @rewind($this->filehandle);
  339.  
  340.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  341.             if (count($row)<5{
  342.                 /** address book is corrupted. */
  343.                 global $color;
  344.                 error_box(_("Address book is corrupted. Required fields are missing."),$color);
  345.                 die('</body></html>');
  346.             else {
  347.                 if(strtolower($row[$field]== $value{
  348.                     return array('nickname'  => $row[0],
  349.                                  'name'      => $row[1' ' $row[2],
  350.                                  'firstname' => $row[1],
  351.                                  'lastname'  => $row[2],
  352.                                  'email'     => $row[3],
  353.                                  'label'     => $row[4],
  354.                                  'backend'   => $this->bnum,
  355.                                  'source'    => &$this->sname);
  356.                 }
  357.             }
  358.         }
  359.  
  360.         return array();
  361.     }
  362.  
  363.     /**
  364.      * List all addresses
  365.      * @return array list of all addresses
  366.      */
  367.     function list_addr({
  368.         // check if listing is not disabled
  369.         if(isset($this->listing&& !$this->listing{
  370.             return array();
  371.         }
  372.  
  373.         $res array();
  374.         $this->open();
  375.         @rewind($this->filehandle);
  376.  
  377.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  378.             if (count($row)<5{
  379.                 /** address book is corrupted. */
  380.                 global $color;
  381.                 error_box(_("Address book is corrupted. Required fields are missing."),$color);
  382.                 die('</body></html>');
  383.             else {
  384.                 array_push($resarray('nickname'  => $row[0],
  385.                                        'name'      => $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.         $nl_str array("\r","\n");
  422.         $data str_replace($nl_str' '$data);
  423.  
  424.         /**
  425.          * Make sure that entry fits into allocated record space.
  426.          * One byte is reserved for linefeed
  427.          */
  428.         if (strlen($data>= $this->line_length{
  429.             return $this->set_error(_("Address book entry is too big"));
  430.         }
  431.  
  432.         /* Add linefeed at end */
  433.         $data $data "\n";
  434.  
  435.         /* Reopen file, just to be sure */
  436.         $this->open(true);
  437.         if(!$this->writeable{
  438.             return $this->set_error(_("Address book is read-only"));
  439.         }
  440.  
  441.         /* Lock the file */
  442.         if(!$this->lock()) {
  443.             return $this->set_error(_("Could not lock datafile"));
  444.         }
  445.  
  446.         /* Write */
  447.         $r sq_fwrite($this->filehandle$data);
  448.  
  449.         /* Unlock file */
  450.         $this->unlock();
  451.  
  452.         /* Test write result */
  453.         if($r === FALSE{
  454.             /* Fail */
  455.             $this->set_error(_("Write to address book failed"));
  456.             return FALSE;
  457.         }
  458.  
  459.         return TRUE;
  460.     }
  461.  
  462.     /**
  463.      * Delete address
  464.      * @param string $alias alias that has to be deleted
  465.      * @return bool 
  466.      */
  467.     function remove($alias{
  468.         if(!$this->writeable{
  469.             return $this->set_error(_("Address book is read-only"));
  470.         }
  471.  
  472.         /* Lock the file to make sure we're the only process working
  473.          * on it. */
  474.         if(!$this->lock()) {
  475.             return $this->set_error(_("Could not lock datafile"));
  476.         }
  477.  
  478.         /* Read file into memory, ignoring nicknames to delete */
  479.         @rewind($this->filehandle);
  480.         $i 0;
  481.         $rows array();
  482.         while($row @fgetcsv($this->filehandle$this->line_length'|')) {
  483.             if(!in_array($row[0]$alias)) {
  484.                 $rows[$i++$row;
  485.             }
  486.         }
  487.  
  488.         /* Write data back */
  489.         if(!$this->overwrite($rows)) {
  490.             $this->unlock();
  491.             return false;
  492.         }
  493.  
  494.         $this->unlock();
  495.         return true;
  496.     }
  497.  
  498.     /**
  499.      * Modify address
  500.      * @param string $alias modified alias
  501.      * @param array $userdata new data
  502.      * @return bool true, if operation successful
  503.      */
  504.     function modify($alias$userdata{
  505.         if(!$this->writeable{
  506.             return $this->set_error(_("Address book is read-only"));
  507.         }
  508.  
  509.         /* See if user exists */
  510.         $ret $this->lookup($alias);
  511.         if(empty($ret)) {
  512.             // i18n: don't use html formating in translation
  513.             return $this->set_error(sprintf(_("User \"%s\" does not exist")$alias));
  514.         }
  515.  
  516.         /* If the alias changed, see if the new alias exists */
  517.         if (strtolower($alias!= strtolower($userdata['nickname'])) {
  518.             $ret $this->lookup($userdata['nickname']);
  519.             if (!empty($ret)) {
  520.                 return $this->set_error(sprintf(_("User \"%s\" already exists")$userdata['nickname']));
  521.             }
  522.         }
  523.  
  524.         /* calculate userdata size */
  525.         $data $this->quotevalue($userdata['nickname']'|'
  526.             . $this->quotevalue($userdata['firstname']'|'
  527.             . $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) '|'
  528.             . $this->quotevalue($userdata['email']'|'
  529.             . $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
  530.         /* make sure that it fits into allocated space */
  531.         if (strlen($data>= $this->line_length{
  532.             return $this->set_error(_("Address book entry is too big"));
  533.         }
  534.  
  535.         /* Lock the file to make sure we're the only process working
  536.          * on it. */
  537.         if(!$this->lock()) {
  538.             return $this->set_error(_("Could not lock datafile"));
  539.         }
  540.  
  541.         /* Read file into memory, modifying the data for the
  542.          * user identified by $alias */
  543.         $this->open(true);
  544.         @rewind($this->filehandle);
  545.         $i 0;
  546.         $rows array();
  547.         while($row @fgetcsv($this->filehandle$this->line_length'|')) {
  548.             if(strtolower($row[0]!= strtolower($alias)) {
  549.                 $rows[$i++$row;
  550.             else {
  551.                 $rows[$i++array(=> $userdata['nickname'],
  552.                                     => $userdata['firstname'],
  553.                                     => (!empty($userdata['lastname'])?$userdata['lastname']:''),
  554.                                     => $userdata['email'],
  555.                                     => (!empty($userdata['label'])?$userdata['label']:''));
  556.             }
  557.         }
  558.  
  559.         /* Write data back */
  560.         if(!$this->overwrite($rows)) {
  561.             $this->unlock();
  562.             return false;
  563.         }
  564.  
  565.         $this->unlock();
  566.         return true;
  567.     }
  568.  
  569.     /**
  570.      * Function for quoting values before saving
  571.      * @param string $value string that has to be quoted
  572.      * @param string quoted string
  573.      */
  574.     function quotevalue($value{
  575.         /* Quote the field if it contains | or ". Double quotes need to
  576.          * be replaced with "" */
  577.         if(stristr($value'"'|| stristr($value'|')) {
  578.             $value '"' str_replace('"''""'$value'"';
  579.         }
  580.         return $value;
  581.     }
  582.  
  583. /* End of class abook_local_file */

Documentation generated on Sun, 20 Apr 2014 04:20:12 +0200 by phpDocumentor 1.4.3