adding phpdoc blocks in addressbook classes (one more backend to go)
[squirrelmail.git] / functions / abook_local_file.php
CommitLineData
5100704d 1<?php
35586184 2/**
3 * abook_local_file.php
4 *
82d304a0 5 * Copyright (c) 1999-2004 The SquirrelMail Project Team
35586184 6 * Licensed under the GNU GPL. For full terms see the file COPYING.
7 *
a9d318b0 8 * @version $Id$
d6c32258 9 * @package squirrelmail
a9d318b0 10 * @subpackage addressbook
35586184 11 */
5100704d 12
d6c32258 13/**
147e5af3 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 *</pre>
26 * NOTE. This class should not be used directly. Use the
27 * "AddressBook" class instead.
8f6f9ba5 28 * @package squirrelmail
d6c32258 29 */
35586184 30class abook_local_file extends addressbook_backend {
4272758c 31 /**
32 * Backend type
33 * @var string
34 */
06b4facd 35 var $btype = 'local';
4272758c 36 /**
37 * Backend name
38 * @var string
39 */
06b4facd 40 var $bname = 'local_file';
41
4272758c 42 /**
43 * File used to store data
44 * @var string
45 */
46 var $filename = '';
47 /**
48 * File handle
49 * @var object
50 */
06b4facd 51 var $filehandle = 0;
4272758c 52 /**
53 * Create file, if it not present
54 * @var bool
55 */
56 var $create = false;
57 /**
58 * Detect, if address book is writeable by checking file permisions
59 * @var bool
60 */
61 var $detect_writeable = true;
62 /**
63 * Control write access to address book
64 *
65 * Option does not have any effect, if 'detect_writeable' is 'true'
66 * @var bool
67 */
68 var $writeable = false;
69 /**
70 * Umask of the file
71 * @var string
72 */
06b4facd 73 var $umask;
74
75 /* ========================== Private ======================= */
76
147e5af3 77 /**
78 * Constructor
79 * @param array $param backend options
80 * @return bool
81 */
06b4facd 82 function abook_local_file($param) {
83 $this->sname = _("Personal address book");
84 $this->umask = Umask();
85
86 if(is_array($param)) {
87 if(empty($param['filename'])) {
88 return $this->set_error('Invalid parameters');
89 }
90 if(!is_string($param['filename'])) {
91 return $this->set_error($param['filename'] . ': '.
92 _("Not a file name"));
93 }
94
95 $this->filename = $param['filename'];
96
147e5af3 97 if(isset($param['create'])) {
98 $this->create = $param['create'];
06b4facd 99 }
100 if(isset($param['umask'])) {
101 $this->umask = $param['umask'];
102 }
4272758c 103 if(isset($param['name'])) {
06b4facd 104 $this->sname = $param['name'];
105 }
4272758c 106 if(isset($param['detect_writeable'])) {
107 $this->detect_writeable = $param['detect_writeable'];
108 }
109 if(!empty($param['writeable'])) {
110 $this->writeable = $param['writeable'];
111 }
62f7daa5 112
06b4facd 113 $this->open(true);
114 } else {
115 $this->set_error('Invalid argument to constructor');
116 }
117 }
118
147e5af3 119 /**
120 * Open the addressbook file and store the file pointer.
62f7daa5 121 * Use $file as the file to open, or the class' own
122 * filename property. If $param is empty and file is
147e5af3 123 * open, do nothing.
124 * @param bool $new is file already opened
125 * @return bool
126 */
06b4facd 127 function open($new = false) {
128 $this->error = '';
129 $file = $this->filename;
130 $create = $this->create;
4272758c 131 $fopenmode = ($this->writeable ? 'a+' : 'r');
62f7daa5 132
06b4facd 133 /* Return true is file is open and $new is unset */
134 if($this->filehandle && !$new) {
135 return true;
136 }
62f7daa5 137
06b4facd 138 /* Check that new file exitsts */
139 if((!(file_exists($file) && is_readable($file))) && !$create) {
140 return $this->set_error("$file: " . _("No such file or directory"));
141 }
62f7daa5 142
06b4facd 143 /* Close old file, if any */
144 if($this->filehandle) { $this->close(); }
62f7daa5 145
06b4facd 146 umask($this->umask);
4272758c 147 if (! $this->detect_writeable) {
148 $fh = @fopen($file,$fopenmode);
149 if ($fh) {
150 $this->filehandle = &$fh;
151 $this->filename = $file;
152 } else {
153 return $this->set_error("$file: " . _("Open failed"));
154 }
06b4facd 155 } else {
4272758c 156 /* Open file. First try to open for reading and writing,
157 * but fall back to read only. */
158 $fh = @fopen($file, 'a+');
06b4facd 159 if($fh) {
160 $this->filehandle = &$fh;
161 $this->filename = $file;
4272758c 162 $this->writeable = true;
06b4facd 163 } else {
4272758c 164 $fh = @fopen($file, 'r');
165 if($fh) {
166 $this->filehandle = &$fh;
167 $this->filename = $file;
168 $this->writeable = false;
169 } else {
170 return $this->set_error("$file: " . _("Open failed"));
171 }
06b4facd 172 }
173 }
174 return true;
175 }
176
147e5af3 177 /** Close the file and forget the filehandle */
06b4facd 178 function close() {
179 @fclose($this->filehandle);
180 $this->filehandle = 0;
181 $this->filename = '';
182 $this->writable = false;
183 }
184
147e5af3 185 /** Lock the datafile - try 20 times in 5 seconds */
06b4facd 186 function lock() {
187 for($i = 0 ; $i < 20 ; $i++) {
62f7daa5 188 if(flock($this->filehandle, 2 + 4))
06b4facd 189 return true;
190 else
191 usleep(250000);
192 }
193 return false;
194 }
195
147e5af3 196 /** Unlock the datafile */
06b4facd 197 function unlock() {
198 return flock($this->filehandle, 3);
199 }
200
147e5af3 201 /**
202 * Overwrite the file with data from $rows
203 * NOTE! Previous locks are broken by this function
204 * @param array $rows new data
205 * @return bool
206 */
06b4facd 207 function overwrite(&$rows) {
01265fba 208 $this->unlock();
dabef6fd 209 $newfh = @fopen($this->filename.'.tmp', 'w');
210
06b4facd 211 if(!$newfh) {
dabef6fd 212 return $this->set_error($this->filename. '.tmp:' . _("Open failed"));
06b4facd 213 }
62f7daa5 214
dabef6fd 215 for($i = 0, $cnt=sizeof($rows) ; $i < $cnt ; $i++) {
06b4facd 216 if(is_array($rows[$i])) {
dabef6fd 217 for($j = 0, $cnt_part=count($rows[$i]) ; $j < $cnt_part ; $j++) {
77ec28e9 218 $rows[$i][$j] = $this->quotevalue($rows[$i][$j]);
219 }
3ecad5e6 220 $tmpwrite = sq_fwrite($newfh, join('|', $rows[$i]) . "\n");
221 if ($tmpwrite === FALSE) {
dabef6fd 222 return $this->set_error($this->filename . '.tmp:' . _("Write failed"));
223 }
06b4facd 224 }
62f7daa5 225 }
06b4facd 226
227 fclose($newfh);
baa59994 228 if (!@copy($this->filename . '.tmp' , $this->filename)) {
dabef6fd 229 return $this->set_error($this->filename . ':' . _("Unable to update"));
baa59994 230 }
dabef6fd 231 @unlink($this->filename . '.tmp');
06b4facd 232 $this->unlock();
233 $this->open(true);
234 return true;
235 }
62f7daa5 236
06b4facd 237 /* ========================== Public ======================== */
62f7daa5 238
147e5af3 239 /**
240 * Search the file
241 * @param string $expr search expression
242 * @return array search results
243 */
06b4facd 244 function search($expr) {
245
246 /* To be replaced by advanded search expression parsing */
247 if(is_array($expr)) { return; }
62f7daa5 248
06b4facd 249 /* Make regexp from glob'ed expression
250 * May want to quote other special characters like (, ), -, [, ], etc. */
251 $expr = str_replace('?', '.', $expr);
252 $expr = str_replace('*', '.*', $expr);
62f7daa5 253
06b4facd 254 $res = array();
255 if(!$this->open()) {
256 return false;
257 }
258 @rewind($this->filehandle);
62f7daa5 259
06b4facd 260 while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
261 $line = join(' ', $row);
262 if(eregi($expr, $line)) {
263 array_push($res, array('nickname' => $row[0],
264 'name' => $row[1] . ' ' . $row[2],
265 'firstname' => $row[1],
266 'lastname' => $row[2],
267 'email' => $row[3],
268 'label' => $row[4],
269 'backend' => $this->bnum,
270 'source' => &$this->sname));
271 }
272 }
62f7daa5 273
06b4facd 274 return $res;
275 }
62f7daa5 276
147e5af3 277 /**
278 * Lookup alias
279 * @param string $alias alias
280 * @return array search results
281 */
06b4facd 282 function lookup($alias) {
283 if(empty($alias)) {
284 return array();
285 }
286
287 $alias = strtolower($alias);
62f7daa5 288
06b4facd 289 $this->open();
290 @rewind($this->filehandle);
62f7daa5 291
06b4facd 292 while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
293 if(strtolower($row[0]) == $alias) {
294 return array('nickname' => $row[0],
295 'name' => $row[1] . ' ' . $row[2],
296 'firstname' => $row[1],
297 'lastname' => $row[2],
298 'email' => $row[3],
299 'label' => $row[4],
300 'backend' => $this->bnum,
301 'source' => &$this->sname);
302 }
303 }
62f7daa5 304
06b4facd 305 return array();
306 }
307
147e5af3 308 /**
309 * List all addresses
310 * @return array list of all addresses
311 */
06b4facd 312 function list_addr() {
313 $res = array();
314 $this->open();
315 @rewind($this->filehandle);
62f7daa5 316
06b4facd 317 while ($row = @fgetcsv($this->filehandle, 2048, '|')) {
318 array_push($res, array('nickname' => $row[0],
319 'name' => $row[1] . ' ' . $row[2],
320 'firstname' => $row[1],
321 'lastname' => $row[2],
322 'email' => $row[3],
323 'label' => $row[4],
324 'backend' => $this->bnum,
325 'source' => &$this->sname));
326 }
327 return $res;
328 }
329
147e5af3 330 /**
331 * Add address
332 * @param array $userdata new data
333 * @return bool
334 */
06b4facd 335 function add($userdata) {
336 if(!$this->writeable) {
337 return $this->set_error(_("Addressbook is read-only"));
338 }
339 /* See if user exists already */
340 $ret = $this->lookup($userdata['nickname']);
341 if(!empty($ret)) {
342 return $this->set_error(sprintf(_("User '%s' already exist"),
343 $ret['nickname']));
344 }
62f7daa5 345
06b4facd 346 /* Here is the data to write */
77ec28e9 347 $data = $this->quotevalue($userdata['nickname']) . '|' .
348 $this->quotevalue($userdata['firstname']) . '|' .
349 $this->quotevalue($userdata['lastname']) . '|' .
350 $this->quotevalue($userdata['email']) . '|' .
351 $this->quotevalue($userdata['label']);
352
06b4facd 353 /* Strip linefeeds */
354 $data = ereg_replace("[\r\n]", ' ', $data);
355 /* Add linefeed at end */
356 $data = $data . "\n";
62f7daa5 357
06b4facd 358 /* Reopen file, just to be sure */
359 $this->open(true);
360 if(!$this->writeable) {
361 return $this->set_error(_("Addressbook is read-only"));
362 }
62f7daa5 363
06b4facd 364 /* Lock the file */
365 if(!$this->lock()) {
366 return $this->set_error(_("Could not lock datafile"));
367 }
62f7daa5 368
06b4facd 369 /* Write */
3ecad5e6 370 $r = sq_fwrite($this->filehandle, $data);
62f7daa5 371
06b4facd 372 /* Unlock file */
373 $this->unlock();
62f7daa5 374
3ecad5e6 375 /* Test write result */
376 if($r === FALSE) {
147e5af3 377 /* Fail */
378 $this->set_error(_("Write to addressbook failed"));
379 return FALSE;
380 }
62f7daa5 381
3ecad5e6 382 return TRUE;
06b4facd 383 }
384
147e5af3 385 /**
386 * Delete address
387 * @param string $alias alias that has to be deleted
388 * @return bool
389 */
06b4facd 390 function remove($alias) {
391 if(!$this->writeable) {
392 return $this->set_error(_("Addressbook is read-only"));
393 }
62f7daa5 394
06b4facd 395 /* Lock the file to make sure we're the only process working
396 * on it. */
397 if(!$this->lock()) {
398 return $this->set_error(_("Could not lock datafile"));
399 }
62f7daa5 400
06b4facd 401 /* Read file into memory, ignoring nicknames to delete */
402 @rewind($this->filehandle);
403 $i = 0;
404 $rows = array();
405 while($row = @fgetcsv($this->filehandle, 2048, '|')) {
406 if(!in_array($row[0], $alias)) {
407 $rows[$i++] = $row;
408 }
409 }
62f7daa5 410
06b4facd 411 /* Write data back */
412 if(!$this->overwrite($rows)) {
413 $this->unlock();
414 return false;
415 }
62f7daa5 416
06b4facd 417 $this->unlock();
418 return true;
419 }
420
147e5af3 421 /**
422 * Modify address
423 * @param string $alias modified alias
424 * @param array $userdata new data
425 * @return bool true, if operation successful
426 */
06b4facd 427 function modify($alias, $userdata) {
428 if(!$this->writeable) {
429 return $this->set_error(_("Addressbook is read-only"));
430 }
62f7daa5 431
06b4facd 432 /* See if user exists */
433 $ret = $this->lookup($alias);
434 if(empty($ret)) {
435 return $this->set_error(sprintf(_("User '%s' does not exist"),
436 $alias));
437 }
62f7daa5 438
06b4facd 439 /* Lock the file to make sure we're the only process working
440 * on it. */
441 if(!$this->lock()) {
442 return $this->set_error(_("Could not lock datafile"));
443 }
62f7daa5 444
445 /* Read file into memory, modifying the data for the
06b4facd 446 * user identified by $alias */
447 $this->open(true);
448 @rewind($this->filehandle);
449 $i = 0;
450 $rows = array();
451 while($row = @fgetcsv($this->filehandle, 2048, '|')) {
452 if(strtolower($row[0]) != strtolower($alias)) {
453 $rows[$i++] = $row;
454 } else {
455 $rows[$i++] = array(0 => $userdata['nickname'],
456 1 => $userdata['firstname'],
457 2 => $userdata['lastname'],
62f7daa5 458 3 => $userdata['email'],
06b4facd 459 4 => $userdata['label']);
460 }
461 }
62f7daa5 462
06b4facd 463 /* Write data back */
464 if(!$this->overwrite($rows)) {
465 $this->unlock();
466 return false;
467 }
62f7daa5 468
06b4facd 469 $this->unlock();
470 return true;
471 }
62f7daa5 472
147e5af3 473 /**
474 * Function for quoting values before saving
475 * @param string $value string that has to be quoted
476 * @param string quoted string
477 */
77ec28e9 478 function quotevalue($value) {
479 /* Quote the field if it contains | or ". Double quotes need to
480 * be replaced with "" */
481 if(ereg("[|\"]", $value)) {
482 $value = '"' . str_replace('"', '""', $value) . '"';
483 }
484 return $value;
485 }
486
06b4facd 487} /* End of class abook_local_file */
62f7daa5 488?>