5100704d |
1 | <?php |
4b4abf93 |
2 | |
35586184 |
3 | /** |
4 | * abook_local_file.php |
5 | * |
701e7bee |
6 | * @copyright 1999-2014 The SquirrelMail Project Team |
4b4abf93 |
7 | * @license http://opensource.org/licenses/gpl-license.php GNU Public License |
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. |
675357d2 |
24 | * ? name => name of address book. |
91e0dccc |
25 | * ? detect_writeable => detect address book access permissions by |
675357d2 |
26 | * checking file permissions. |
27 | * ? writeable => allow writing into address book. Used only when |
28 | * detect_writeable is set to false. |
e59a9c41 |
29 | * ? listing => enable/disable listing |
147e5af3 |
30 | *</pre> |
31 | * NOTE. This class should not be used directly. Use the |
32 | * "AddressBook" class instead. |
8f6f9ba5 |
33 | * @package squirrelmail |
d6c32258 |
34 | */ |
35586184 |
35 | class abook_local_file extends addressbook_backend { |
4272758c |
36 | /** |
37 | * Backend type |
91e0dccc |
38 | * @var string |
4272758c |
39 | */ |
06b4facd |
40 | var $btype = 'local'; |
4272758c |
41 | /** |
42 | * Backend name |
43 | * @var string |
44 | */ |
06b4facd |
45 | var $bname = 'local_file'; |
46 | |
4272758c |
47 | /** |
48 | * File used to store data |
49 | * @var string |
50 | */ |
51 | var $filename = ''; |
52 | /** |
53 | * File handle |
54 | * @var object |
55 | */ |
06b4facd |
56 | var $filehandle = 0; |
4272758c |
57 | /** |
58 | * Create file, if it not present |
59 | * @var bool |
60 | */ |
61 | var $create = false; |
62 | /** |
63 | * Detect, if address book is writeable by checking file permisions |
64 | * @var bool |
65 | */ |
66 | var $detect_writeable = true; |
67 | /** |
68 | * Control write access to address book |
69 | * |
70 | * Option does not have any effect, if 'detect_writeable' is 'true' |
71 | * @var bool |
72 | */ |
73 | var $writeable = false; |
e59a9c41 |
74 | /** |
75 | * controls listing of address book |
76 | * @var bool |
77 | */ |
78 | var $listing = true; |
4272758c |
79 | /** |
80 | * Umask of the file |
81 | * @var string |
82 | */ |
06b4facd |
83 | var $umask; |
7311c377 |
84 | /** |
85 | * Sets max entry size (number of bytes used for all address book fields |
86 | * (including escapes) + 4 delimiters + 1 linefeed) |
87 | * @var integer |
88 | * @since 1.5.2 |
89 | */ |
90 | var $line_length = 2048; |
06b4facd |
91 | |
92 | /* ========================== Private ======================= */ |
93 | |
147e5af3 |
94 | /** |
95 | * Constructor |
96 | * @param array $param backend options |
97 | * @return bool |
98 | */ |
06b4facd |
99 | function abook_local_file($param) { |
232fb714 |
100 | $this->sname = _("Personal Address Book"); |
06b4facd |
101 | $this->umask = Umask(); |
102 | |
103 | if(is_array($param)) { |
104 | if(empty($param['filename'])) { |
105 | return $this->set_error('Invalid parameters'); |
106 | } |
107 | if(!is_string($param['filename'])) { |
108 | return $this->set_error($param['filename'] . ': '. |
109 | _("Not a file name")); |
110 | } |
111 | |
112 | $this->filename = $param['filename']; |
113 | |
147e5af3 |
114 | if(isset($param['create'])) { |
115 | $this->create = $param['create']; |
06b4facd |
116 | } |
117 | if(isset($param['umask'])) { |
118 | $this->umask = $param['umask']; |
119 | } |
4272758c |
120 | if(isset($param['name'])) { |
06b4facd |
121 | $this->sname = $param['name']; |
122 | } |
4272758c |
123 | if(isset($param['detect_writeable'])) { |
124 | $this->detect_writeable = $param['detect_writeable']; |
125 | } |
126 | if(!empty($param['writeable'])) { |
127 | $this->writeable = $param['writeable']; |
128 | } |
e59a9c41 |
129 | if(isset($param['listing'])) { |
130 | $this->listing = $param['listing']; |
131 | } |
7311c377 |
132 | if(isset($param['line_length']) && ! empty($param['line_length'])) { |
133 | $this->line_length = (int) $param['line_length']; |
134 | } |
62f7daa5 |
135 | |
06b4facd |
136 | $this->open(true); |
137 | } else { |
138 | $this->set_error('Invalid argument to constructor'); |
139 | } |
140 | } |
141 | |
147e5af3 |
142 | /** |
143 | * Open the addressbook file and store the file pointer. |
62f7daa5 |
144 | * Use $file as the file to open, or the class' own |
145 | * filename property. If $param is empty and file is |
147e5af3 |
146 | * open, do nothing. |
147 | * @param bool $new is file already opened |
148 | * @return bool |
149 | */ |
06b4facd |
150 | function open($new = false) { |
151 | $this->error = ''; |
152 | $file = $this->filename; |
153 | $create = $this->create; |
0a496c76 |
154 | $fopenmode = (($this->writeable && sq_is_writable($file)) ? 'a+' : 'r'); |
62f7daa5 |
155 | |
06b4facd |
156 | /* Return true is file is open and $new is unset */ |
157 | if($this->filehandle && !$new) { |
158 | return true; |
159 | } |
62f7daa5 |
160 | |
06b4facd |
161 | /* Check that new file exitsts */ |
162 | if((!(file_exists($file) && is_readable($file))) && !$create) { |
163 | return $this->set_error("$file: " . _("No such file or directory")); |
164 | } |
62f7daa5 |
165 | |
06b4facd |
166 | /* Close old file, if any */ |
167 | if($this->filehandle) { $this->close(); } |
62f7daa5 |
168 | |
06b4facd |
169 | umask($this->umask); |
4272758c |
170 | if (! $this->detect_writeable) { |
171 | $fh = @fopen($file,$fopenmode); |
172 | if ($fh) { |
173 | $this->filehandle = &$fh; |
174 | $this->filename = $file; |
175 | } else { |
176 | return $this->set_error("$file: " . _("Open failed")); |
177 | } |
06b4facd |
178 | } else { |
4272758c |
179 | /* Open file. First try to open for reading and writing, |
180 | * but fall back to read only. */ |
181 | $fh = @fopen($file, 'a+'); |
06b4facd |
182 | if($fh) { |
183 | $this->filehandle = &$fh; |
184 | $this->filename = $file; |
4272758c |
185 | $this->writeable = true; |
06b4facd |
186 | } else { |
4272758c |
187 | $fh = @fopen($file, 'r'); |
188 | if($fh) { |
189 | $this->filehandle = &$fh; |
190 | $this->filename = $file; |
191 | $this->writeable = false; |
192 | } else { |
193 | return $this->set_error("$file: " . _("Open failed")); |
194 | } |
06b4facd |
195 | } |
196 | } |
197 | return true; |
198 | } |
199 | |
147e5af3 |
200 | /** Close the file and forget the filehandle */ |
06b4facd |
201 | function close() { |
202 | @fclose($this->filehandle); |
203 | $this->filehandle = 0; |
204 | $this->filename = ''; |
205 | $this->writable = false; |
206 | } |
207 | |
147e5af3 |
208 | /** Lock the datafile - try 20 times in 5 seconds */ |
06b4facd |
209 | function lock() { |
210 | for($i = 0 ; $i < 20 ; $i++) { |
62f7daa5 |
211 | if(flock($this->filehandle, 2 + 4)) |
06b4facd |
212 | return true; |
213 | else |
214 | usleep(250000); |
215 | } |
216 | return false; |
217 | } |
218 | |
147e5af3 |
219 | /** Unlock the datafile */ |
06b4facd |
220 | function unlock() { |
221 | return flock($this->filehandle, 3); |
222 | } |
223 | |
147e5af3 |
224 | /** |
225 | * Overwrite the file with data from $rows |
226 | * NOTE! Previous locks are broken by this function |
227 | * @param array $rows new data |
228 | * @return bool |
229 | */ |
06b4facd |
230 | function overwrite(&$rows) { |
01265fba |
231 | $this->unlock(); |
dabef6fd |
232 | $newfh = @fopen($this->filename.'.tmp', 'w'); |
233 | |
06b4facd |
234 | if(!$newfh) { |
dabef6fd |
235 | return $this->set_error($this->filename. '.tmp:' . _("Open failed")); |
06b4facd |
236 | } |
62f7daa5 |
237 | |
dabef6fd |
238 | for($i = 0, $cnt=sizeof($rows) ; $i < $cnt ; $i++) { |
06b4facd |
239 | if(is_array($rows[$i])) { |
dabef6fd |
240 | for($j = 0, $cnt_part=count($rows[$i]) ; $j < $cnt_part ; $j++) { |
77ec28e9 |
241 | $rows[$i][$j] = $this->quotevalue($rows[$i][$j]); |
242 | } |
3ecad5e6 |
243 | $tmpwrite = sq_fwrite($newfh, join('|', $rows[$i]) . "\n"); |
244 | if ($tmpwrite === FALSE) { |
dabef6fd |
245 | return $this->set_error($this->filename . '.tmp:' . _("Write failed")); |
246 | } |
06b4facd |
247 | } |
62f7daa5 |
248 | } |
06b4facd |
249 | |
250 | fclose($newfh); |
baa59994 |
251 | if (!@copy($this->filename . '.tmp' , $this->filename)) { |
dabef6fd |
252 | return $this->set_error($this->filename . ':' . _("Unable to update")); |
baa59994 |
253 | } |
dabef6fd |
254 | @unlink($this->filename . '.tmp'); |
53d36779 |
255 | @chmod($this->filename, 0600); |
06b4facd |
256 | $this->unlock(); |
257 | $this->open(true); |
258 | return true; |
259 | } |
62f7daa5 |
260 | |
06b4facd |
261 | /* ========================== Public ======================== */ |
62f7daa5 |
262 | |
147e5af3 |
263 | /** |
264 | * Search the file |
265 | * @param string $expr search expression |
266 | * @return array search results |
267 | */ |
06b4facd |
268 | function search($expr) { |
269 | |
270 | /* To be replaced by advanded search expression parsing */ |
271 | if(is_array($expr)) { return; } |
62f7daa5 |
272 | |
327e2d96 |
273 | // don't allow wide search when listing is disabled. |
274 | if ($expr=='*' && ! $this->listing) |
275 | return array(); |
276 | |
63389330 |
277 | // Make regexp from glob'ed expression |
278 | $expr = preg_quote($expr); |
279 | $expr = str_replace(array('\\?', '\\*'), array('.', '.*'), $expr); |
62f7daa5 |
280 | |
06b4facd |
281 | $res = array(); |
282 | if(!$this->open()) { |
283 | return false; |
284 | } |
285 | @rewind($this->filehandle); |
62f7daa5 |
286 | |
7311c377 |
287 | while ($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { |
288 | if (count($row)<5) { |
289 | /** |
290 | * address book is corrupted. |
291 | */ |
292 | global $oTemplate; |
293 | error_box(_("Address book is corrupted. Required fields are missing.")); |
294 | $oTemplate->display('footer.tpl'); |
295 | die(); |
296 | } else { |
7311c377 |
297 | /** |
298 | * TODO: regexp search is supported only in local_file backend. |
299 | * Do we check format of regexp or ignore errors? |
300 | */ |
b7910e12 |
301 | // errors on preg_match call are suppressed in order to prevent display of regexp compilation errors |
63389330 |
302 | if (@preg_match('/' . $expr . '/i', $row[0]) // nickname |
303 | || @preg_match('/' . $expr . '/i', $row[1]) // firstname |
304 | || @preg_match('/' . $expr . '/i', $row[2]) // lastname |
305 | || @preg_match('/' . $expr . '/i', $row[3])) { // email |
7311c377 |
306 | array_push($res, array('nickname' => $row[0], |
307 | 'name' => $this->fullname($row[1], $row[2]), |
308 | 'firstname' => $row[1], |
309 | 'lastname' => $row[2], |
310 | 'email' => $row[3], |
311 | 'label' => $row[4], |
312 | 'backend' => $this->bnum, |
313 | 'source' => &$this->sname)); |
314 | } |
06b4facd |
315 | } |
316 | } |
62f7daa5 |
317 | |
06b4facd |
318 | return $res; |
319 | } |
62f7daa5 |
320 | |
147e5af3 |
321 | /** |
503c7650 |
322 | * Lookup an address by the indicated field. |
323 | * |
324 | * @param string $value The value to look up |
325 | * @param integer $field The field to look in, should be one |
326 | * of the SM_ABOOK_FIELD_* constants |
327 | * defined in include/constants.php |
328 | * (OPTIONAL; defaults to nickname field) |
bf55ebab |
329 | * NOTE: uniqueness is only guaranteed |
330 | * when the nickname field is used here; |
331 | * otherwise, the first matching address |
332 | * is returned. |
503c7650 |
333 | * |
334 | * @return array Array with lookup results when the value |
335 | * was found, an empty array if the value was |
336 | * not found. |
337 | * |
147e5af3 |
338 | */ |
503c7650 |
339 | function lookup($value, $field=SM_ABOOK_FIELD_NICKNAME) { |
340 | if(empty($value)) { |
06b4facd |
341 | return array(); |
342 | } |
343 | |
503c7650 |
344 | $value = strtolower($value); |
62f7daa5 |
345 | |
06b4facd |
346 | $this->open(); |
347 | @rewind($this->filehandle); |
62f7daa5 |
348 | |
7311c377 |
349 | while ($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { |
350 | if (count($row)<5) { |
351 | /** |
352 | * address book is corrupted. |
353 | */ |
354 | global $oTemplate; |
355 | error_box(_("Address book is corrupted. Required fields are missing.")); |
356 | $oTemplate->display('footer.tpl'); |
357 | die(); |
358 | } else { |
503c7650 |
359 | if(strtolower($row[$field]) == $value) { |
7311c377 |
360 | return array('nickname' => $row[0], |
361 | 'name' => $this->fullname($row[1], $row[2]), |
362 | 'firstname' => $row[1], |
363 | 'lastname' => $row[2], |
364 | 'email' => $row[3], |
365 | 'label' => $row[4], |
366 | 'backend' => $this->bnum, |
367 | 'source' => &$this->sname); |
368 | } |
06b4facd |
369 | } |
370 | } |
62f7daa5 |
371 | |
06b4facd |
372 | return array(); |
373 | } |
374 | |
147e5af3 |
375 | /** |
376 | * List all addresses |
377 | * @return array list of all addresses |
378 | */ |
06b4facd |
379 | function list_addr() { |
380 | $res = array(); |
e59a9c41 |
381 | |
382 | if(isset($this->listing) && !$this->listing) { |
383 | return array(); |
384 | } |
385 | |
06b4facd |
386 | $this->open(); |
387 | @rewind($this->filehandle); |
62f7daa5 |
388 | |
7311c377 |
389 | while ($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { |
390 | if (count($row)<5) { |
391 | /** |
392 | * address book is corrupted. Don't be nice to people that |
393 | * violate address book formating. |
394 | */ |
395 | global $oTemplate; |
396 | error_box(_("Address book is corrupted. Required fields are missing.")); |
397 | $oTemplate->display('footer.tpl'); |
398 | die(); |
399 | } else { |
400 | array_push($res, array('nickname' => $row[0], |
401 | 'name' => $this->fullname($row[1], $row[2]), |
402 | 'firstname' => $row[1], |
403 | 'lastname' => $row[2], |
404 | 'email' => $row[3], |
405 | 'label' => $row[4], |
406 | 'backend' => $this->bnum, |
407 | 'source' => &$this->sname)); |
408 | } |
06b4facd |
409 | } |
410 | return $res; |
411 | } |
412 | |
147e5af3 |
413 | /** |
414 | * Add address |
415 | * @param array $userdata new data |
416 | * @return bool |
417 | */ |
06b4facd |
418 | function add($userdata) { |
419 | if(!$this->writeable) { |
35235328 |
420 | return $this->set_error(_("Address book is read-only")); |
06b4facd |
421 | } |
422 | /* See if user exists already */ |
423 | $ret = $this->lookup($userdata['nickname']); |
424 | if(!empty($ret)) { |
2706a0b1 |
425 | // i18n: don't use html formating in translation |
426 | return $this->set_error(sprintf(_("User \"%s\" already exists"),$ret['nickname'])); |
06b4facd |
427 | } |
62f7daa5 |
428 | |
06b4facd |
429 | /* Here is the data to write */ |
77ec28e9 |
430 | $data = $this->quotevalue($userdata['nickname']) . '|' . |
431 | $this->quotevalue($userdata['firstname']) . '|' . |
8419c13b |
432 | $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) . '|' . |
77ec28e9 |
433 | $this->quotevalue($userdata['email']) . '|' . |
8419c13b |
434 | $this->quotevalue((!empty($userdata['label'])?$userdata['label']:'')); |
77ec28e9 |
435 | |
06b4facd |
436 | /* Strip linefeeds */ |
b7910e12 |
437 | $nl_str = array("\r","\n"); |
438 | $data = str_replace($nl_str, ' ', $data); |
7311c377 |
439 | |
440 | /** |
441 | * Make sure that entry fits into allocated record space. |
442 | * One byte is reserved for linefeed |
443 | */ |
444 | if (strlen($data) >= $this->line_length) { |
445 | return $this->set_error(_("Address book entry is too big")); |
446 | } |
447 | |
06b4facd |
448 | /* Add linefeed at end */ |
449 | $data = $data . "\n"; |
62f7daa5 |
450 | |
06b4facd |
451 | /* Reopen file, just to be sure */ |
452 | $this->open(true); |
453 | if(!$this->writeable) { |
35235328 |
454 | return $this->set_error(_("Address book is read-only")); |
06b4facd |
455 | } |
62f7daa5 |
456 | |
06b4facd |
457 | /* Lock the file */ |
458 | if(!$this->lock()) { |
459 | return $this->set_error(_("Could not lock datafile")); |
460 | } |
62f7daa5 |
461 | |
06b4facd |
462 | /* Write */ |
3ecad5e6 |
463 | $r = sq_fwrite($this->filehandle, $data); |
62f7daa5 |
464 | |
06b4facd |
465 | /* Unlock file */ |
466 | $this->unlock(); |
62f7daa5 |
467 | |
3ecad5e6 |
468 | /* Test write result */ |
469 | if($r === FALSE) { |
147e5af3 |
470 | /* Fail */ |
35235328 |
471 | $this->set_error(_("Write to address book failed")); |
147e5af3 |
472 | return FALSE; |
473 | } |
62f7daa5 |
474 | |
3ecad5e6 |
475 | return TRUE; |
06b4facd |
476 | } |
477 | |
147e5af3 |
478 | /** |
479 | * Delete address |
480 | * @param string $alias alias that has to be deleted |
481 | * @return bool |
482 | */ |
06b4facd |
483 | function remove($alias) { |
484 | if(!$this->writeable) { |
35235328 |
485 | return $this->set_error(_("Address book is read-only")); |
06b4facd |
486 | } |
62f7daa5 |
487 | |
06b4facd |
488 | /* Lock the file to make sure we're the only process working |
489 | * on it. */ |
490 | if(!$this->lock()) { |
491 | return $this->set_error(_("Could not lock datafile")); |
492 | } |
62f7daa5 |
493 | |
06b4facd |
494 | /* Read file into memory, ignoring nicknames to delete */ |
495 | @rewind($this->filehandle); |
496 | $i = 0; |
497 | $rows = array(); |
7311c377 |
498 | while($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { |
06b4facd |
499 | if(!in_array($row[0], $alias)) { |
500 | $rows[$i++] = $row; |
501 | } |
502 | } |
62f7daa5 |
503 | |
06b4facd |
504 | /* Write data back */ |
505 | if(!$this->overwrite($rows)) { |
506 | $this->unlock(); |
507 | return false; |
508 | } |
62f7daa5 |
509 | |
06b4facd |
510 | $this->unlock(); |
511 | return true; |
512 | } |
513 | |
147e5af3 |
514 | /** |
515 | * Modify address |
516 | * @param string $alias modified alias |
517 | * @param array $userdata new data |
518 | * @return bool true, if operation successful |
519 | */ |
06b4facd |
520 | function modify($alias, $userdata) { |
521 | if(!$this->writeable) { |
35235328 |
522 | return $this->set_error(_("Address book is read-only")); |
06b4facd |
523 | } |
62f7daa5 |
524 | |
06b4facd |
525 | /* See if user exists */ |
526 | $ret = $this->lookup($alias); |
527 | if(empty($ret)) { |
2706a0b1 |
528 | // i18n: don't use html formating in translation |
529 | return $this->set_error(sprintf(_("User \"%s\" does not exist"),$alias)); |
06b4facd |
530 | } |
849164a1 |
531 | |
532 | /* If the alias changed, see if the new alias exists */ |
533 | if (strtolower($alias) != strtolower($userdata['nickname'])) { |
534 | $ret = $this->lookup($userdata['nickname']); |
535 | if (!empty($ret)) { |
536 | return $this->set_error(sprintf(_("User \"%s\" already exists"), $userdata['nickname'])); |
537 | } |
538 | } |
539 | |
06b4facd |
540 | /* Lock the file to make sure we're the only process working |
541 | * on it. */ |
542 | if(!$this->lock()) { |
543 | return $this->set_error(_("Could not lock datafile")); |
544 | } |
62f7daa5 |
545 | |
7311c377 |
546 | /* calculate userdata size */ |
547 | $data = $this->quotevalue($userdata['nickname']) . '|' |
548 | . $this->quotevalue($userdata['firstname']) . '|' |
549 | . $this->quotevalue((!empty($userdata['lastname'])?$userdata['lastname']:'')) . '|' |
550 | . $this->quotevalue($userdata['email']) . '|' |
551 | . $this->quotevalue((!empty($userdata['label'])?$userdata['label']:'')); |
552 | /* make sure that it fits into allocated space */ |
553 | if (strlen($data) >= $this->line_length) { |
554 | return $this->set_error(_("Address book entry is too big")); |
555 | } |
556 | |
62f7daa5 |
557 | /* Read file into memory, modifying the data for the |
06b4facd |
558 | * user identified by $alias */ |
559 | $this->open(true); |
560 | @rewind($this->filehandle); |
561 | $i = 0; |
562 | $rows = array(); |
7311c377 |
563 | while($row = @fgetcsv($this->filehandle, $this->line_length, '|')) { |
06b4facd |
564 | if(strtolower($row[0]) != strtolower($alias)) { |
565 | $rows[$i++] = $row; |
566 | } else { |
567 | $rows[$i++] = array(0 => $userdata['nickname'], |
568 | 1 => $userdata['firstname'], |
8419c13b |
569 | 2 => (!empty($userdata['lastname'])?$userdata['lastname']:''), |
62f7daa5 |
570 | 3 => $userdata['email'], |
8419c13b |
571 | 4 => (!empty($userdata['label'])?$userdata['label']:'')); |
06b4facd |
572 | } |
573 | } |
62f7daa5 |
574 | |
06b4facd |
575 | /* Write data back */ |
576 | if(!$this->overwrite($rows)) { |
577 | $this->unlock(); |
578 | return false; |
579 | } |
62f7daa5 |
580 | |
06b4facd |
581 | $this->unlock(); |
582 | return true; |
583 | } |
62f7daa5 |
584 | |
147e5af3 |
585 | /** |
586 | * Function for quoting values before saving |
587 | * @param string $value string that has to be quoted |
588 | * @param string quoted string |
589 | */ |
77ec28e9 |
590 | function quotevalue($value) { |
591 | /* Quote the field if it contains | or ". Double quotes need to |
592 | * be replaced with "" */ |
bbb2bab5 |
593 | if(stristr($value, '"') || stristr($value, '|')) { |
77ec28e9 |
594 | $value = '"' . str_replace('"', '""', $value) . '"'; |
595 | } |
596 | return $value; |
597 | } |
6b0fe53b |
598 | } |