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