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