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