abook_local_file.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <?php
  2. /**
  3. * abook_local_file.php
  4. *
  5. * Copyright (c) 1999-2001 The Squirrelmail Development Team
  6. * Licensed under the GNU GPL. For full terms see the file COPYING.
  7. *
  8. * Backend for addressbook as a pipe separated file
  9. *
  10. * An array with the following elements must be passed to
  11. * the class constructor (elements marked ? are optional):
  12. *
  13. * filename => path to addressbook file
  14. * ? create => if true: file is created if it does not exist.
  15. * ? umask => umask set before opening file.
  16. *
  17. * NOTE. This class should not be used directly. Use the
  18. * "AddressBook" class instead.
  19. *
  20. * $Id$
  21. */
  22. /*****************************************************************/
  23. /*** THIS FILE NEEDS TO HAVE ITS FORMATTING FIXED!!! ***/
  24. /*** PLEASE DO SO AND REMOVE THIS COMMENT SECTION. ***/
  25. /*** + Base level indent should begin at left margin, as ***/
  26. /*** the first line of the class definition below. ***/
  27. /*** + All identation should consist of four space blocks ***/
  28. /*** + Tab characters are evil. ***/
  29. /*** + all comments should use "slash-star ... star-slash" ***/
  30. /*** style -- no pound characters, no slash-slash style ***/
  31. /*** + FLOW CONTROL STATEMENTS (if, while, etc) SHOULD ***/
  32. /*** ALWAYS USE { AND } CHARACTERS!!! ***/
  33. /*** + Please use ' instead of ", when possible. Note " ***/
  34. /*** should always be used in _( ) function calls. ***/
  35. /*** Thank you for your help making the SM code more readable. ***/
  36. /*****************************************************************/
  37. class abook_local_file extends addressbook_backend {
  38. var $btype = 'local';
  39. var $bname = 'local_file';
  40. var $filename = '';
  41. var $filehandle = 0;
  42. var $create = false;
  43. var $umask;
  44. // ========================== Private =======================
  45. // Constructor
  46. function abook_local_file($param) {
  47. $this->sname = _("Personal address book");
  48. $this->umask = Umask();
  49. if(is_array($param)) {
  50. if(empty($param['filename']))
  51. return $this->set_error('Invalid parameters');
  52. if(!is_string($param['filename']))
  53. return $this->set_error($param['filename'] . ': '.
  54. _("Not a file name"));
  55. $this->filename = $param['filename'];
  56. if($param['create'])
  57. $this->create = true;
  58. if(isset($param['umask']))
  59. $this->umask = $param['umask'];
  60. if(!empty($param['name']))
  61. $this->sname = $param['name'];
  62. $this->open(true);
  63. } else {
  64. $this->set_error('Invalid argument to constructor');
  65. }
  66. }
  67. // Open the addressbook file and store the file pointer.
  68. // Use $file as the file to open, or the class' own
  69. // filename property. If $param is empty and file is
  70. // open, do nothing.
  71. function open($new = false) {
  72. $this->error = '';
  73. $file = $this->filename;
  74. $create = $this->create;
  75. // Return true is file is open and $new is unset
  76. if($this->filehandle && !$new)
  77. return true;
  78. // Check that new file exitsts
  79. if((!(file_exists($file) && is_readable($file))) && !$create)
  80. return $this->set_error("$file: " . _("No such file or directory"));
  81. // Close old file, if any
  82. if($this->filehandle) $this->close();
  83. // Open file. First try to open for reading and writing,
  84. // but fall back to read only.
  85. umask($this->umask);
  86. $fh = @fopen($file, 'a+');
  87. if($fh) {
  88. $this->filehandle = &$fh;
  89. $this->filename = $file;
  90. $this->writeable = true;
  91. } else {
  92. $fh = @fopen($file, 'r');
  93. if($fh) {
  94. $this->filehandle = &$fh;
  95. $this->filename = $file;
  96. $this->writeable = false;
  97. } else {
  98. return $this->set_error("$file: " . _("Open failed"));
  99. }
  100. }
  101. return true;
  102. }
  103. // Close the file and forget the filehandle
  104. function close() {
  105. @fclose($this->filehandle);
  106. $this->filehandle = 0;
  107. $this->filename = '';
  108. $this->writable = false;
  109. }
  110. // Lock the datafile - try 20 times in 5 seconds
  111. function lock() {
  112. for($i = 0 ; $i < 20 ; $i++) {
  113. if(flock($this->filehandle, 2 + 4))
  114. return true;
  115. else
  116. usleep(250000);
  117. }
  118. return false;
  119. }
  120. // Lock the datafile
  121. function unlock() {
  122. return flock($this->filehandle, 3);
  123. }
  124. // Overwrite the file with data from $rows
  125. // NOTE! Previous locks are broken by this function
  126. function overwrite(&$rows) {
  127. $newfh = @fopen($this->filename, 'w');
  128. if(!$newfh)
  129. return $this->set_error("$file: " . _("Open failed"));
  130. for($i = 0 ; $i < sizeof($rows) ; $i++) {
  131. if(is_array($rows[$i]))
  132. fwrite($newfh, join('|', $rows[$i]) . "\n");
  133. }
  134. fclose($newfh);
  135. $this->unlock();
  136. $this->open(true);
  137. return true;
  138. }
  139. // ========================== Public ========================
  140. // Search the file
  141. function search($expr) {
  142. // To be replaced by advanded search expression parsing
  143. if(is_array($expr)) return;
  144. // Make regexp from glob'ed expression
  145. // May want to quote other special characters like (, ), -, [, ], etc.
  146. $expr = str_replace('?', '.', $expr);
  147. $expr = str_replace('*', '.*', $expr);
  148. $res = array();
  149. if(!$this->open())
  150. return false;
  151. @rewind($this->filehandle);
  152. while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
  153. $line = join(' ', $row);
  154. if(eregi($expr, $line)) {
  155. array_push($res, array('nickname' => $row[0],
  156. 'name' => $row[1] . ' ' . $row[2],
  157. 'firstname' => $row[1],
  158. 'lastname' => $row[2],
  159. 'email' => $row[3],
  160. 'label' => $row[4],
  161. 'backend' => $this->bnum,
  162. 'source' => &$this->sname));
  163. }
  164. }
  165. return $res;
  166. }
  167. // Lookup alias
  168. function lookup($alias) {
  169. if(empty($alias))
  170. return array();
  171. $alias = strtolower($alias);
  172. $this->open();
  173. @rewind($this->filehandle);
  174. while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
  175. if(strtolower($row[0]) == $alias) {
  176. return array('nickname' => $row[0],
  177. 'name' => $row[1] . ' ' . $row[2],
  178. 'firstname' => $row[1],
  179. 'lastname' => $row[2],
  180. 'email' => $row[3],
  181. 'label' => $row[4],
  182. 'backend' => $this->bnum,
  183. 'source' => &$this->sname);
  184. }
  185. }
  186. return array();
  187. }
  188. // List all addresses
  189. function list_addr() {
  190. $res = array();
  191. $this->open();
  192. @rewind($this->filehandle);
  193. while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
  194. array_push($res, array('nickname' => $row[0],
  195. 'name' => $row[1] . ' ' . $row[2],
  196. 'firstname' => $row[1],
  197. 'lastname' => $row[2],
  198. 'email' => $row[3],
  199. 'label' => $row[4],
  200. 'backend' => $this->bnum,
  201. 'source' => &$this->sname));
  202. }
  203. return $res;
  204. }
  205. // Add address
  206. function add($userdata) {
  207. if(!$this->writeable)
  208. return $this->set_error(_("Addressbook is read-only"));
  209. // See if user exist already
  210. $ret = $this->lookup($userdata['nickname']);
  211. if(!empty($ret))
  212. return $this->set_error(sprintf(_("User '%s' already exist"),
  213. $ret['nickname']));
  214. // Here is the data to write
  215. $data = $userdata['nickname'] . '|' . $userdata['firstname'] . '|' .
  216. $userdata['lastname'] . '|' . $userdata['email'] . '|' .
  217. $userdata['label'];
  218. // Strip linefeeds
  219. $data = ereg_replace("[\r\n]", ' ', $data);
  220. // Add linefeed at end
  221. $data = $data . "\n";
  222. // Reopen file, just to be sure
  223. $this->open(true);
  224. if(!$this->writeable)
  225. return $this->set_error(_("Addressbook is read-only"));
  226. // Lock the file
  227. if(!$this->lock())
  228. return $this->set_error(_("Could not lock datafile"));
  229. // Write
  230. $r = fwrite($this->filehandle, $data);
  231. // Unlock file
  232. $this->unlock();
  233. // Test write result and exit if OK
  234. if($r > 0) return true;
  235. // Fail
  236. $this->set_error(_("Write to addressbook failed"));
  237. return false;
  238. }
  239. // Delete address
  240. function remove($alias) {
  241. if(!$this->writeable)
  242. return $this->set_error(_("Addressbook is read-only"));
  243. // Lock the file to make sure we're the only process working
  244. // on it.
  245. if(!$this->lock())
  246. return $this->set_error(_("Could not lock datafile"));
  247. // Read file into memory, ignoring nicknames to delete
  248. @rewind($this->filehandle);
  249. $i = 0;
  250. $rows = array();
  251. while($row = @fgetcsv($this->filehandle, 2048, '|')) {
  252. if(!in_array($row[0], $alias))
  253. $rows[$i++] = $row;
  254. }
  255. // Write data back
  256. if(!$this->overwrite($rows)) {
  257. $this->unlock();
  258. return false;
  259. }
  260. $this->unlock();
  261. return true;
  262. }
  263. // Modify address
  264. function modify($alias, $userdata) {
  265. if(!$this->writeable)
  266. return $this->set_error(_("Addressbook is read-only"));
  267. // See if user exist
  268. $ret = $this->lookup($alias);
  269. if(empty($ret))
  270. return $this->set_error(sprintf(_("User '%s' does not exist"),
  271. $alias));
  272. // Lock the file to make sure we're the only process working
  273. // on it.
  274. if(!$this->lock())
  275. return $this->set_error(_("Could not lock datafile"));
  276. // Read file into memory, modifying the data for the
  277. // user identifyed by $alias
  278. $this->open(true);
  279. @rewind($this->filehandle);
  280. $i = 0;
  281. $rows = array();
  282. while($row = @fgetcsv($this->filehandle, 2048, '|')) {
  283. if(strtolower($row[0]) != strtolower($alias)) {
  284. $rows[$i++] = $row;
  285. } else {
  286. $rows[$i++] = array(0 => $userdata['nickname'],
  287. 1 => $userdata['firstname'],
  288. 2 => $userdata['lastname'],
  289. 3 => $userdata['email'],
  290. 4 => $userdata['label']);
  291. }
  292. }
  293. // Write data back
  294. if(!$this->overwrite($rows)) {
  295. $this->unlock();
  296. return false;
  297. }
  298. $this->unlock();
  299. return true;
  300. }
  301. } // End of class abook_local_file
  302. ?>