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