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 14420 2014-01-01 20:33:20Z 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.  *</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 && sq_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.         @chmod($this->filename0600);
  255.         $this->unlock();
  256.         $this->open(true);
  257.         return true;
  258.     }
  259.  
  260.     /* ========================== Public ======================== */
  261.  
  262.     /**
  263.      * Search the file
  264.      * @param string $expr search expression
  265.      * @return array search results
  266.      */
  267.     function search($expr{
  268.  
  269.         /* To be replaced by advanded search expression parsing */
  270.         if(is_array($expr)) return}
  271.  
  272.         // don't allow wide search when listing is disabled.
  273.         if ($expr=='*' && $this->listing)
  274.             return array();
  275.  
  276.         // Make regexp from glob'ed expression
  277.         $expr preg_quote($expr);
  278.         $expr str_replace(array('\\?''\\*')array('.''.*')$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.                 /**
  297.                  * TODO: regexp search is supported only in local_file backend.
  298.                  * Do we check format of regexp or ignore errors?
  299.                  */
  300.                 // errors on preg_match call are suppressed in order to prevent display of regexp compilation errors
  301.                 if (@preg_match('/' $expr '/i'$row[0])    // nickname
  302.                  || @preg_match('/' $expr '/i'$row[1])    // firstname
  303.                  || @preg_match('/' $expr '/i'$row[2])    // lastname
  304.                  || @preg_match('/' $expr '/i'$row[3])) // email
  305.                     array_push($resarray('nickname'  => $row[0],
  306.                         'name'      => $this->fullname($row[1]$row[2]),
  307.                         'firstname' => $row[1],
  308.                         'lastname'  => $row[2],
  309.                         'email'     => $row[3],
  310.                         'label'     => $row[4],
  311.                         'backend'   => $this->bnum,
  312.                         'source'    => &$this->sname));
  313.                 }
  314.             }
  315.         }
  316.  
  317.         return $res;
  318.     }
  319.  
  320.     /**
  321.      * Lookup an address by the indicated field.
  322.      *
  323.      * @param string  $value The value to look up
  324.      * @param integer $field The field to look in, should be one
  325.      *                        of the SM_ABOOK_FIELD_* constants
  326.      *                        defined in include/constants.php
  327.      *                        (OPTIONAL; defaults to nickname field)
  328.      *                        NOTE: uniqueness is only guaranteed
  329.      *                        when the nickname field is used here;
  330.      *                        otherwise, the first matching address
  331.      *                        is returned.
  332.      *
  333.      * @return array Array with lookup results when the value
  334.      *                was found, an empty array if the value was
  335.      *                not found.
  336.      *
  337.      */
  338.     function lookup($value$field=SM_ABOOK_FIELD_NICKNAME{
  339.         if(empty($value)) {
  340.             return array();
  341.         }
  342.  
  343.         $value strtolower($value);
  344.  
  345.         $this->open();
  346.         @rewind($this->filehandle);
  347.  
  348.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  349.             if (count($row)<5{
  350.                 /**
  351.                  * address book is corrupted.
  352.                  */
  353.                 global $oTemplate;
  354.                 error_box(_("Address book is corrupted. Required fields are missing."));
  355.                 $oTemplate->display('footer.tpl');
  356.                 die();
  357.             else {
  358.                 if(strtolower($row[$field]== $value{
  359.                    return array('nickname'  => $row[0],
  360.                       'name'      => $this->fullname($row[1]$row[2]),
  361.                       'firstname' => $row[1],
  362.                       'lastname'  => $row[2],
  363.                       'email'     => $row[3],
  364.                       'label'     => $row[4],
  365.                       'backend'   => $this->bnum,
  366.                       'source'    => &$this->sname);
  367.                 }
  368.             }
  369.         }
  370.  
  371.         return array();
  372.     }
  373.  
  374.     /**
  375.      * List all addresses
  376.      * @return array list of all addresses
  377.      */
  378.     function list_addr({
  379.         $res array();
  380.  
  381.         if(isset($this->listing&& !$this->listing{
  382.             return array();
  383.         }
  384.  
  385.         $this->open();
  386.         @rewind($this->filehandle);
  387.  
  388.         while ($row @fgetcsv($this->filehandle$this->line_length'|')) {
  389.             if (count($row)<5{
  390.                 /**
  391.                  * address book is corrupted. Don't be nice to people that 
  392.                  * violate address book formating.
  393.                  */
  394.                 global $oTemplate;
  395.                 error_box(_("Address book is corrupted. Required fields are missing."));
  396.                 $oTemplate->display('footer.tpl');
  397.                 die();
  398.             else {
  399.                 array_push($resarray('nickname'  => $row[0],
  400.                     'name'      => $this->fullname($row[1]$row[2]),
  401.                     'firstname' => $row[1],
  402.                     'lastname'  => $row[2],
  403.                     'email'     => $row[3],
  404.                     'label'     => $row[4],
  405.                     'backend'   => $this->bnum,
  406.                     'source'    => &$this->sname));
  407.             }
  408.         }
  409.         return $res;
  410.     }
  411.  
  412.     /**
  413.      * Add address
  414.      * @param array $userdata new data
  415.      * @return bool 
  416.      */
  417.     function add($userdata{
  418.         if(!$this->writeable{
  419.             return $this->set_error(_("Address book is read-only"));
  420.         }
  421.         /* See if user exists already */
  422.         $ret $this->lookup($userdata['nickname']);
  423.         if(!empty($ret)) {
  424.             // i18n: don't use html formating in translation
  425.             return $this->set_error(sprintf(_("User \"%s\" already exists"),$ret['nickname']));
  426.         }
  427.  
  428.         /* Here is the data to write */
  429.         $data $this->quotevalue($userdata['nickname']'|' .
  430.                 $this->quotevalue($userdata['firstname']'|' .
  431.                 $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) '|' .
  432.                 $this->quotevalue($userdata['email']'|' .
  433.                 $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
  434.  
  435.         /* Strip linefeeds */
  436.         $nl_str array("\r","\n");
  437.         $data str_replace($nl_str' '$data);
  438.  
  439.         /**
  440.          * Make sure that entry fits into allocated record space.
  441.          * One byte is reserved for linefeed
  442.          */
  443.         if (strlen($data>= $this->line_length{
  444.             return $this->set_error(_("Address book entry is too big"));
  445.         }
  446.  
  447.         /* Add linefeed at end */
  448.         $data $data "\n";
  449.  
  450.         /* Reopen file, just to be sure */
  451.         $this->open(true);
  452.         if(!$this->writeable{
  453.             return $this->set_error(_("Address book is read-only"));
  454.         }
  455.  
  456.         /* Lock the file */
  457.         if(!$this->lock()) {
  458.             return $this->set_error(_("Could not lock datafile"));
  459.         }
  460.  
  461.         /* Write */
  462.         $r sq_fwrite($this->filehandle$data);
  463.  
  464.         /* Unlock file */
  465.         $this->unlock();
  466.  
  467.         /* Test write result */
  468.         if($r === FALSE{
  469.             /* Fail */
  470.             $this->set_error(_("Write to address book failed"));
  471.             return FALSE;
  472.         }
  473.  
  474.         return TRUE;
  475.     }
  476.  
  477.     /**
  478.      * Delete address
  479.      * @param string $alias alias that has to be deleted
  480.      * @return bool 
  481.      */
  482.     function remove($alias{
  483.         if(!$this->writeable{
  484.             return $this->set_error(_("Address book is read-only"));
  485.         }
  486.  
  487.         /* Lock the file to make sure we're the only process working
  488.          * on it. */
  489.         if(!$this->lock()) {
  490.             return $this->set_error(_("Could not lock datafile"));
  491.         }
  492.  
  493.         /* Read file into memory, ignoring nicknames to delete */
  494.         @rewind($this->filehandle);
  495.         $i 0;
  496.         $rows array();
  497.         while($row @fgetcsv($this->filehandle$this->line_length'|')) {
  498.             if(!in_array($row[0]$alias)) {
  499.                 $rows[$i++$row;
  500.             }
  501.         }
  502.  
  503.         /* Write data back */
  504.         if(!$this->overwrite($rows)) {
  505.             $this->unlock();
  506.             return false;
  507.         }
  508.  
  509.         $this->unlock();
  510.         return true;
  511.     }
  512.  
  513.     /**
  514.      * Modify address
  515.      * @param string $alias modified alias
  516.      * @param array $userdata new data
  517.      * @return bool true, if operation successful
  518.      */
  519.     function modify($alias$userdata{
  520.         if(!$this->writeable{
  521.             return $this->set_error(_("Address book is read-only"));
  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.         /* Lock the file to make sure we're the only process working
  540.          * on it. */
  541.         if(!$this->lock()) {
  542.             return $this->set_error(_("Could not lock datafile"));
  543.         }
  544.  
  545.         /* calculate userdata size */
  546.         $data $this->quotevalue($userdata['nickname']'|'
  547.             . $this->quotevalue($userdata['firstname']'|'
  548.             . $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) '|'
  549.             . $this->quotevalue($userdata['email']'|'
  550.             . $this->quotevalue((!empty($userdata['label'])?$userdata['label']:''));
  551.         /* make sure that it fits into allocated space */
  552.         if (strlen($data>= $this->line_length{
  553.             return $this->set_error(_("Address book entry is too big"));
  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. }

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