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 14466 2014-06-16 07:27:02Z 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.  
  407.         // NB: if you want to check for some unwanted characters
  408.         //     or other problems, do so here like this:
  409.         // TODO: Should pull all validation code out into a separate function
  410.         //if (strpos($userdata['nickname'], ' ')) {
  411.         //    return $this->set_error(_("Nickname contains illegal characters"));
  412.         //}
  413.  
  414.         /* See if user exists already */
  415.         $ret $this->lookup($userdata['nickname']);
  416.         if(!empty($ret)) {
  417.             // i18n: don't use html formating in translation
  418.             return $this->set_error(sprintf(_("User \"%s\" already exists")$ret['nickname']));
  419.         }
  420.  
  421.         /* Here is the data to write */
  422.         $data $this->quotevalue($userdata['nickname']'|' .
  423.                 $this->quotevalue($userdata['firstname']'|' .
  424.                 $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) '|' .
  425.                 $this->quotevalue($userdata['email']'|' .
  426.                 $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
  427.  
  428.         /* Strip linefeeds */
  429.         $nl_str array("\r","\n");
  430.         $data str_replace($nl_str' '$data);
  431.  
  432.         /**
  433.          * Make sure that entry fits into allocated record space.
  434.          * One byte is reserved for linefeed
  435.          */
  436.         if (strlen($data>= $this->line_length{
  437.             return $this->set_error(_("Address book entry is too big"));
  438.         }
  439.  
  440.         /* Add linefeed at end */
  441.         $data $data "\n";
  442.  
  443.         /* Reopen file, just to be sure */
  444.         $this->open(true);
  445.         if(!$this->writeable{
  446.             return $this->set_error(_("Address book is read-only"));
  447.         }
  448.  
  449.         /* Lock the file */
  450.         if(!$this->lock()) {
  451.             return $this->set_error(_("Could not lock datafile"));
  452.         }
  453.  
  454.         /* Write */
  455.         $r sq_fwrite($this->filehandle$data);
  456.  
  457.         /* Unlock file */
  458.         $this->unlock();
  459.  
  460.         /* Test write result */
  461.         if($r === FALSE{
  462.             /* Fail */
  463.             $this->set_error(_("Write to address book failed"));
  464.             return FALSE;
  465.         }
  466.  
  467.         return TRUE;
  468.     }
  469.  
  470.     /**
  471.      * Delete address
  472.      * @param string $alias alias that has to be deleted
  473.      * @return bool 
  474.      */
  475.     function remove($alias{
  476.         if(!$this->writeable{
  477.             return $this->set_error(_("Address book is read-only"));
  478.         }
  479.  
  480.         /* Lock the file to make sure we're the only process working
  481.          * on it. */
  482.         if(!$this->lock()) {
  483.             return $this->set_error(_("Could not lock datafile"));
  484.         }
  485.  
  486.         /* Read file into memory, ignoring nicknames to delete */
  487.         @rewind($this->filehandle);
  488.         $i 0;
  489.         $rows array();
  490.         while($row @fgetcsv($this->filehandle$this->line_length'|')) {
  491.             if(!in_array($row[0]$alias)) {
  492.                 $rows[$i++$row;
  493.             }
  494.         }
  495.  
  496.         /* Write data back */
  497.         if(!$this->overwrite($rows)) {
  498.             $this->unlock();
  499.             return false;
  500.         }
  501.  
  502.         $this->unlock();
  503.         return true;
  504.     }
  505.  
  506.     /**
  507.      * Modify address
  508.      * @param string $alias modified alias
  509.      * @param array $userdata new data
  510.      * @return bool true, if operation successful
  511.      */
  512.     function modify($alias$userdata{
  513.         if(!$this->writeable{
  514.             return $this->set_error(_("Address book is read-only"));
  515.         }
  516.  
  517.         // NB: if you want to check for some unwanted characters
  518.         //     or other problems, do so here like this:
  519.         // TODO: Should pull all validation code out into a separate function
  520.         //if (strpos($userdata['nickname'], ' ')) {
  521.         //    return $this->set_error(_("Nickname contains illegal characters"));
  522.         //}
  523.  
  524.         /* See if user exists */
  525.         $ret $this->lookup($alias);
  526.         if(empty($ret)) {
  527.             // i18n: don't use html formating in translation
  528.             return $this->set_error(sprintf(_("User \"%s\" does not exist")$alias));
  529.         }
  530.  
  531.         /* If the alias changed, see if the new alias exists */
  532.         if (strtolower($alias!= strtolower($userdata['nickname'])) {
  533.             $ret $this->lookup($userdata['nickname']);
  534.             if (!empty($ret)) {
  535.                 return $this->set_error(sprintf(_("User \"%s\" already exists")$userdata['nickname']));
  536.             }
  537.         }
  538.  
  539.         /* calculate userdata size */
  540.         $data $this->quotevalue($userdata['nickname']'|'
  541.             . $this->quotevalue($userdata['firstname']'|'
  542.             . $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) '|'
  543.             . $this->quotevalue($userdata['email']'|'
  544.             . $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
  545.         /* make sure that it fits into allocated space */
  546.         if (strlen($data>= $this->line_length{
  547.             return $this->set_error(_("Address book entry is too big"));
  548.         }
  549.  
  550.         /* Lock the file to make sure we're the only process working
  551.          * on it. */
  552.         if(!$this->lock()) {
  553.             return $this->set_error(_("Could not lock datafile"));
  554.         }
  555.  
  556.         /* Read file into memory, modifying the data for the
  557.          * user identified by $alias */
  558.         $this->open(true);
  559.         @rewind($this->filehandle);
  560.         $i 0;
  561.         $rows array();
  562.         while($row @fgetcsv($this->filehandle$this->line_length'|')) {
  563.             if(strtolower($row[0]!= strtolower($alias)) {
  564.                 $rows[$i++$row;
  565.             else {
  566.                 $rows[$i++array(=> $userdata['nickname'],
  567.                                     => $userdata['firstname'],
  568.                                     => (!empty($userdata['lastname'])?$userdata['lastname']:''),
  569.                                     => $userdata['email'],
  570.                                     => (!empty($userdata['label'])?$userdata['label']:''));
  571.             }
  572.         }
  573.  
  574.         /* Write data back */
  575.         if(!$this->overwrite($rows)) {
  576.             $this->unlock();
  577.             return false;
  578.         }
  579.  
  580.         $this->unlock();
  581.         return true;
  582.     }
  583.  
  584.     /**
  585.      * Function for quoting values before saving
  586.      * @param string $value string that has to be quoted
  587.      * @param string quoted string
  588.      */
  589.     function quotevalue($value{
  590.         /* Quote the field if it contains | or ". Double quotes need to
  591.          * be replaced with "" */
  592.         if(stristr($value'"'|| stristr($value'|')) {
  593.             $value '"' str_replace('"''""'$value'"';
  594.         }
  595.         return $value;
  596.     }
  597.  
  598. /* End of class abook_local_file */

Documentation generated on Thu, 31 Jul 2014 04:20:18 +0200 by phpDocumentor 1.4.3