1: <?php
2: /**
3: * Caching for the Kolab free/busy data.
4: *
5: * @package Kolab_FreeBusy
6: */
7:
8: /** We require the iCalendar library to build the free/busy list */
9: require_once 'Horde/Icalendar.php';
10: require_once 'Horde/Icalendar/Vfreebusy.php';
11:
12: /**
13: * The Horde_Kolab_FreeBusy_Cache:: class provides functionality to store
14: * prepared free/busy data for quick retrieval.
15: *
16: * Copyright 2004-2008 Klarälvdalens Datakonsult AB
17: *
18: * See the enclosed file COPYING for license information (LGPL). If you
19: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
20: *
21: * @author Gunnar Wrobel <p@rdus.de>
22: * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
23: * @package Kolab_FreeBusy
24: */
25: class Horde_Kolab_FreeBusy_Cache {
26:
27: /**
28: * The directory that should be used for caching.
29: *
30: * @var string
31: */
32: var $_cache_dir;
33:
34: /**
35: * Constructor.
36: *
37: * @param string $cache_dir The cache directory we should use.
38: */
39: function Horde_Kolab_FreeBusy_Cache($cache_dir)
40: {
41: $this->_cache_dir = $cache_dir;
42: }
43:
44: /**
45: * Update the cache information for a calendar.
46: *
47: * @param Horde_Kolab_FreeBusy_Access $access The object holding the
48: * relevant access
49: * parameters.
50: *
51: * @return boolean|PEAR_Error True if successful.
52: */
53: function store($access)
54: {
55: global $conf;
56:
57: /* Now we really need the free/busy library */
58: require_once 'Horde/Kolab/FreeBusy/Imap.php';
59:
60: $fb = new Horde_Kolab_FreeBusy_Imap();
61:
62: $result = $fb->connect($access->imap_folder);
63: if (is_a($result, 'PEAR_Error')) {
64: return $result;
65: }
66:
67: $fbpast = $fbfuture = null;
68: try {
69: if (!empty($access->server_object)) {
70: $result = $access->server_object->get(Horde_Kolab_Server_Object_Kolab_Server::ATTRIBUTE_FBPAST);
71: if (!is_a($result, 'PEAR_Error')) {
72: $fbpast = $result;
73: }
74: }
75: } catch (Horde_Kolab_Server_Exception $e) {
76: Horde::logMessage(sprintf("Failed fetching the k=kolab configuration object. Error was: %s", $e->getMessage()), 'ERR');
77: if (isset($conf['kolab']['freebusy']['past'])) {
78: $fbpast = $conf['kolab']['freebusy']['past'];
79: } else {
80: $fbpast = 10;
81: }
82: }
83:
84: if (!empty($access->owner_object)) {
85: $result = $access->owner_object->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_FBFUTURE);
86: if (!is_a($result, 'PEAR_Error')) {
87: $fbfuture = $result;
88: }
89: }
90:
91: $vCal = $fb->generate(null, null,
92: !empty($fbpast)?$fbpast:0,
93: !empty($fbfuture)?$fbfuture:60,
94: $access->owner,
95: $access->owner_object->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_CN));
96: if (is_a($vCal, 'PEAR_Error')) {
97: $vCal;
98: }
99:
100: $fbfilename = $this->_getFilename($access->folder, $access->owner);
101:
102: $c_pvcal = new Horde_Kolab_FreeBusy_Cache_File_pvcal($this->_cache_dir, $fbfilename);
103:
104: if (!empty($conf['fb']['use_acls'])) {
105: $c_acl = new Horde_Kolab_FreeBusy_Cache_File_acl($this->_cache_dir, $fbfilename);
106: $c_xacl = new Horde_Kolab_FreeBusy_Cache_File_xacl($this->_cache_dir, $fbfilename);
107: }
108:
109: /* missing data means delete the cache files */
110: if (empty($vCal)) {
111: Horde::logMessage(sprintf("No events. Purging cache %s.", $fbfilename), 'DEBUG');
112:
113: $result = $c_pvcal->purge();
114: if (is_a($result, 'PEAR_Error')) {
115: return $result;
116: }
117:
118: if (!empty($conf['fb']['use_acls'])) {
119: $result = $c_acl->purge();
120: if (is_a($result, 'PEAR_Error')) {
121: return $result;
122: }
123: $result = $c_xacl->purge();
124: if (is_a($result, 'PEAR_Error')) {
125: return $result;
126: }
127: }
128: } else {
129: $result = $c_pvcal->storePVcal($vCal);
130: if (is_a($result, 'PEAR_Error')) {
131: return $result;
132: }
133:
134: $relevance = $fb->getRelevance();
135: if (is_a($relevance, 'PEAR_Error')) {
136: return $relevance;
137: }
138:
139: if (!empty($conf['fb']['use_acls'])) {
140: $acl = $fb->getACL();
141: if (is_a($acl, 'PEAR_Error')) {
142: return $acl;
143: }
144:
145: /**
146: * Only store the acl information if the current user
147: * has admin rights on the folder and can actually
148: * retrieve the full ACL information.
149: *
150: * A folder that does not have admin rights for a user
151: * will not be considered relvant for that user unless
152: * it has been triggered by the folder owner before.
153: */
154: $append = false;
155: if (isset($acl[$access->user])) {
156: $myacl = $acl[$access->user];
157: if (strpos($myacl, 'a') !== false) {
158: $append = true;
159: }
160: }
161:
162: $result = $c_acl->storeACL($acl, $relevance, $append);
163: if (is_a($result, 'PEAR_Error')) {
164: return $result;
165: }
166:
167: $xacl = $fb->getExtendedACL();
168: if (is_a($xacl, 'PEAR_Error')) {
169: return $xacl;
170: }
171:
172: $result = $c_xacl->storeXACL($xacl, $acl);
173: if (is_a($result, 'PEAR_Error')) {
174: return $result;
175: }
176: } else {
177: $acl = null;
178: }
179:
180: Horde::logMessage(sprintf("Horde_Kolab_FreeBusy_Cache::store(file=%s, relevance=%s, acl=%s, xacl=%s)", $fbfilename, $relevance, $acl, $xacl), 'DEBUG');
181: }
182: return true;
183: }
184:
185: /**
186: * Load partial free/busy data.
187: *
188: * @param Horde_Kolab_FreeBusy_Access $access The object holding the
189: * relevant access
190: * parameters.
191: * @param boolean $extended Should the data hold the extended
192: * free/busy information?
193: *
194: * @return Horde_Icalendar|PEAR_Error The free/busy data of a
195: * single calendar.
196: */
197: function &loadPartial(&$access, $extended)
198: {
199: global $conf;
200:
201: $file = $this->_getFilename($access->folder, $access->owner);
202:
203: if (!empty($conf['fb']['use_acls'])) {
204: $aclcache = &Horde_Kolab_FreeBusy_Cache_DB_acl::singleton('acl',
205: $this->_cache_dir);
206: if ($extended) {
207: $extended = $this->_allowExtended($file, $access);
208: }
209: }
210:
211: $c_pvcal = new Horde_Kolab_FreeBusy_Cache_File_pvcal($this->_cache_dir, $file);
212: $pvCal = $c_pvcal->loadPVcal($extended);
213: if (is_a($pvCal, 'PEAR_Error')) {
214: return $pvCal;
215: }
216: return $pvCal;
217: }
218:
219: /**
220: * Is extended access to the given file allowed?
221: *
222: * @param string $file Name of the cache file.
223: * @param Horde_Kolab_FreeBusy_Access $access The object holding the
224: * relevant access
225: * parameters.
226: *
227: * @return boolean|PEAR_Error True if extended access is allowed.
228: */
229: function _allowExtended($file, &$access)
230: {
231: if (!isset($access->user_object)) {
232: Horde::logMessage(sprintf("Extended attributes on folder %s disallowed for unknown user.", $access->folder, $access->user), 'DEBUG');
233: return false;
234: }
235:
236: $xaclcache = &Horde_Kolab_FreeBusy_Cache_DB_xacl::singleton('xacl', $this->_cache_dir);
237:
238: /* Check if the calling user has access to the extended information of
239: * the folder we are about to integrate into the free/busy data.
240: */
241: $groups = $access->user_object->getGroupAddresses();
242: if (is_a($groups, 'PEAR_Error')) {
243: return $groups;
244: }
245:
246: $groups[] = $access->user;
247: foreach ($groups as $id) {
248: if ($xaclcache->has($file, $id)) {
249: return true;
250: }
251: }
252: Horde::logMessage(sprintf("Extended attributes on folder %s disallowed for user %s.", $access->folder, $access->user), 'DEBUG');
253: return false;
254: }
255:
256: /**
257: * Get a cache file name depending on the owner of the free/busy
258: * data.
259: *
260: * @param string $folder Name of the calendar folder.
261: * @param string $owner Owner of the calendar folder.
262: *
263: * @return string Name of the correspoding cache file.
264: */
265: function _getFilename($folder, $owner)
266: {
267: if (ereg('(.*)@(.*)', $owner, $regs)) {
268: $owner = $regs[2] . '/' . $regs[1];
269: }
270:
271: return str_replace("\0", '', str_replace('.', '^', $owner . '/' . $folder));
272: }
273:
274: /**
275: * Retrieve external free/busy data.
276: *
277: * @param array $servers The remote servers to query
278: * @param Horde_Kolab_FreeBusy_Access $access The object holding the
279: * relevant access
280: * parameters.
281: *
282: * @return Horde_Icalender The remote free/busy information.
283: */
284: function &_fetchRemote($servers, $access)
285: {
286: $vFb = null;
287:
288: foreach ($servers as $server) {
289:
290: $url = 'https://' . urlencode($access->user) . ':' . urlencode($access->pass)
291: . '@' . $server . $_SERVER['REQUEST_URI'];
292: $remote = @file_get_contents($url);
293: if (!$remote) {
294: $message = sprintf("Unable to read free/busy information from %s",
295: 'https://' . urlencode($access->user) . ':XXX'
296: . '@' . $server . $_SERVER['REQUEST_URI']);
297: Horde::logMessage($message, 'INFO');
298: }
299:
300: $rvCal = new Horde_Icalendar();
301: $result = $rvCal->parsevCalendar($remote);
302:
303: if (is_a($result, 'PEAR_Error')) {
304: $message = sprintf("Unable to parse free/busy information from %s: %s",
305: 'https://' . urlencode($access->user) . ':XXX'
306: . '@' . $server . $_SERVER['REQUEST_URI'],
307: $result->getMessage());
308: Horde::logMessage($message, 'INFO');
309: }
310:
311: $rvFb = &$rvCal->findComponent('vfreebusy');
312: if (!$pvFb) {
313: $message = sprintf("Unable to find free/busy information in data from %s.",
314: 'https://' . urlencode($access->user) . ':XXX'
315: . '@' . $server . $_SERVER['REQUEST_URI']);
316: Horde::logMessage($message, 'INFO');
317: }
318: if ($ets = $rvFb->getAttributeDefault('DTEND', false) !== false) {
319: // PENDING(steffen): Make value configurable
320: if ($ets < time()) {
321: $message = sprintf("free/busy information from %s is too old.",
322: 'https://' . urlencode($access->user) . ':XXX'
323: . '@' . $server . $_SERVER['REQUEST_URI']);
324: Horde::logMessage($message, 'INFO');
325: }
326: }
327: if (!empty($vFb)) {
328: $vFb->merge($rvFb);
329: } else {
330: $vFb = $rvFb;
331: }
332: }
333: return $vFb;
334: }
335:
336: function findAll_readdir($uid, $dirname, &$lst) {
337: if ($dir = @opendir($dirname)) {
338: while (($file = readdir($dir)) !== false) {
339: if ($file == "." || $file == "..")
340: continue;
341:
342: $full_path = $dirname."/".$file;
343:
344: if (is_file($full_path) && preg_match("/(.*)\.x?pvc$/", $file, $matches))
345: $lst[] = $uid."/".$matches[1];
346: else if(is_dir($full_path))
347: $this->findAll_readdir($uid."/".$file, $full_path, $lst);
348: }
349: closedir($dir);
350: }
351: }
352: };
353:
354: /**
355: * A berkeley db based cache for free/busy data.
356: *
357: * Copyright 2004-2008 Klarälvdalens Datakonsult AB
358: *
359: * See the enclosed file COPYING for license information (LGPL). If you
360: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
361: *
362: * @author Gunnar Wrobel <p@rdus.de>
363: * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
364: * @package Kolab_FreeBusy
365: */
366: class Horde_Kolab_FreeBusy_Cache_DB {
367:
368: /**
369: * The directory that should be used for caching.
370: *
371: * @var string
372: */
373: var $_cache_dir;
374:
375: /**
376: * The resource handle into the database.
377: *
378: * @var resource
379: */
380: var $_db = false;
381:
382: /**
383: * The format of the database.
384: *
385: * @var string
386: */
387: var $_dbformat;
388:
389: /**
390: * The type of this cache.
391: *
392: * @var string
393: */
394: var $_type = '';
395:
396: /**
397: * The directory that should be used for caching.
398: *
399: * @var string
400: */
401: function Horde_Kolab_FreeBusy_Cache_DB($cache_dir) {
402: global $conf;
403:
404: $this->_cache_dir = $cache_dir;
405:
406: if (!empty($conf['fb']['dbformat'])) {
407: $this->_dbformat = $conf['fb']['dbformat'];
408: } else {
409: $this->_dbformat = 'db4';
410: }
411:
412: /* make sure that a database really exists before accessing it */
413: if (!file_exists($this->_cache_dir . '/' . $this->_type . 'cache.db')) {
414: $result = $this->_open();
415: if (is_a($result, 'PEAR_Error')) {
416: return $result;
417: }
418: $this->_close();
419: }
420:
421: }
422:
423: /**
424: * Open the database.
425: *
426: * @return boolean|PEAR_Error True if successful.
427: */
428: function _open()
429: {
430: if ($this->_db !== false) {
431: return true;
432: }
433:
434: $dbfile = $this->_cache_dir . '/' . $this->_type . 'cache.db';
435: $this->_db = dba_open($dbfile, 'cd', $this->_dbformat);
436: if ($this->_db === false) {
437: return PEAR::raiseError(sprintf("Unable to open freebusy cache db %s", $dbfile));
438: }
439: return true;
440: }
441:
442: /**
443: * Close the database.
444: */
445: function _close()
446: {
447: if ($this->_db !== false) {
448: dba_close($this->_db);
449: }
450: $this->_db = false;
451: }
452:
453: /**
454: * Set a cache file as irrelevant for a user.
455: *
456: * @param string $filename The cache file to remove.
457: * @param string $uid The user ID.
458: *
459: * @return boolean|PEAR_Error True if successful.
460: */
461: function _remove($filename, $uid)
462: {
463: $result = $this->_open();
464: if (is_a($result, 'PEAR_Error')) {
465: return $result;
466: }
467:
468: if (dba_exists($uid, $this->_db)) {
469: $lst = dba_fetch($uid, $this->_db);
470: $lst = explode(',', $lst);
471: $lst = array_diff($lst, array($filename));
472: $result = dba_replace($uid, join(',', $lst), $this->_db);
473: if ($result === false) {
474: $result = PEAR::raiseError(sprintf("Unable to set db value for uid %s", $uid));
475: }
476: }
477: $this->_close();
478:
479: return $result;
480: }
481:
482: /**
483: * Set a cache file as relevant for a user.
484: *
485: * @param string $filename The cache file to add.
486: * @param string $uid The user ID.
487: *
488: * @return boolean|PEAR_Error True if successful.
489: */
490: function _add($filename, $uid)
491: {
492: if (empty($filename)) {
493: return true;
494: }
495:
496: $result = $this->_open();
497: if (is_a($result, 'PEAR_Error')) {
498: return $result;
499: }
500:
501: if (dba_exists($uid, $this->_db)) {
502: $lst = dba_fetch($uid, $this->_db);
503: $lst = explode(',', $lst);
504: $lst[] = $filename;
505: $result = dba_replace($uid, join(',', array_keys(array_flip($lst))), $this->_db);
506: if ($result === false) {
507: $result = PEAR::raiseError(sprintf("Unable to set db value for uid %s", $uid));
508: }
509: } else {
510: $result = dba_insert($uid, $filename, $this->_db);
511: if ($result === false) {
512: $result = PEAR::raiseError(sprintf("Unable to set db value for uid %s", $uid));
513: }
514: }
515: $this->_close();
516:
517: return $result;
518: }
519:
520: /**
521: * Is the cache file relevant for the user?
522: *
523: * @param string $filename The cache file.
524: * @param string $uid The user ID.
525: *
526: * @return boolean|PEAR_Error True if the cache file is relevant.
527: */
528: function has($filename, $uid)
529: {
530: $result = $this->_open();
531: if (is_a($result, 'PEAR_Error')) {
532: return $result;
533: }
534:
535: $result = false;
536: if (dba_exists($uid, $this->_db)) {
537: $lst = dba_fetch($uid, $this->_db);
538: $lst = explode(',', $lst);
539: $result = in_array($filename, $lst);
540: }
541: $this->_close();
542:
543: return $result;
544: }
545:
546: /**
547: * Get the full list of relevant cache files for a uid.
548: *
549: * @param string $uid The user ID.
550: *
551: * @return array|PEAR_Error The list of cache files.
552: */
553: function get($uid)
554: {
555: $result = $this->_open();
556: if (is_a($result, 'PEAR_Error')) {
557: return $result;
558: }
559:
560: $result = array();
561: if (dba_exists($uid, $this->_db)) {
562: $lst = dba_fetch($uid, $this->_db);
563: $lst = explode(',', $lst);
564: $result = array_filter($lst, array($this, '_notEmpty'));
565: }
566: $this->_close();
567:
568: return $result;
569: }
570:
571: /**
572: * Check if the value is set.
573: *
574: * @param mixed $value The value to check.
575: *
576: * @return boolean True if the value is set.
577: */
578: function _notEmpty($value)
579: {
580: return !empty($value);
581: }
582:
583: /**
584: * Attempts to return a reference to a concrete FreeBusyACLCache
585: * instance. It will only create a new instance if no
586: * FreeBusyACLCache instance currently exists.
587: *
588: * This method must be invoked as:
589: * <code>$var = &FreeBusyACLCache::singleton($cache_dir);</code>
590: *
591: * @static
592: *
593: * @param string $type The type of the cache.
594: * @param string $cache_dir The directory for storing the cache.
595: *
596: * @return FreeBusyACLCache The concrete FreeBusyACLCache
597: * reference, or false on an error.
598: */
599: function &singleton($type, $cache_dir)
600: {
601: static $cachedb = array();
602:
603: $signature = $type . $cache_dir;
604:
605: if (empty($cachedb[$signature])) {
606: $class = 'Horde_Kolab_FreeBusy_Cache_DB_' . $type;
607: $cachedb[$signature] = new $class($cache_dir);
608: }
609:
610: return $cachedb[$signature];
611: }
612: }
613:
614: /**
615: * A berkeley db based cache for free/busy data that holds relevant
616: * cache files based on folder ACLs.
617: *
618: * Copyright 2004-2008 Klarälvdalens Datakonsult AB
619: *
620: * See the enclosed file COPYING for license information (LGPL). If you
621: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
622: *
623: * @author Gunnar Wrobel <p@rdus.de>
624: * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
625: * @package Kolab_FreeBusy
626: */
627: class Horde_Kolab_FreeBusy_Cache_DB_acl extends Horde_Kolab_FreeBusy_Cache_DB {
628:
629: /**
630: * The type of this cache.
631: *
632: * @var string
633: */
634: var $_type = 'acl';
635:
636: /**
637: * Store permissions on a calender folder.
638: *
639: * @param string $filename The cache file representing the calendar folder.
640: * @param array $acl The new ACL.
641: * @param array $oldacl The old ACL.
642: * @param mixed $perm False if all permissions should be revoked, a
643: * single character specifying allowed access
644: * otherwise.
645: *
646: * @return boolean|PEAR_Error True if successful.
647: */
648: function store($filename, $acl, $oldacl, $perm)
649: {
650: /* We remove the filename from all users listed in the old ACL first */
651: foreach ($oldacl as $user => $ac) {
652: $result = $this->_remove($filename, $user);
653: if (is_a($result, 'PEAR_Error')) {
654: return $result;
655: }
656: }
657:
658: /* Now add the filename for all users with the correct permissions */
659: if ($perm !== false ) {
660: foreach ($acl as $user => $ac) {
661: if (strpos($ac, $perm) !== false) {
662: if (!empty($user)) {
663: $result = $this->_add($filename, $user);
664: if (is_a($result, 'PEAR_Error')) {
665: return $result;
666: }
667: }
668: }
669: }
670: }
671:
672: return true;
673: }
674: }
675:
676: /**
677: * A berkeley db based cache for free/busy data that holds relevant
678: * cache files based on extended folder ACLs.
679: *
680: * Copyright 2004-2008 Klarälvdalens Datakonsult AB
681: *
682: * See the enclosed file COPYING for license information (LGPL). If you
683: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
684: *
685: * @author Gunnar Wrobel <p@rdus.de>
686: * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
687: * @package Kolab_FreeBusy
688: */
689: class Horde_Kolab_FreeBusy_Cache_DB_xacl extends Horde_Kolab_FreeBusy_Cache_DB {
690:
691: /**
692: * The type of this cache.
693: *
694: * @var string
695: */
696: var $_type = 'xacl';
697:
698: /**
699: * Store permissions on a calender folder.
700: *
701: * @param string $filename The cache file representing the calendar folder.
702: * @param array $xacl The new extended ACL.
703: * @param array $oldxacl The old extended ACL.
704: *
705: * @return boolean|PEAR_Error True if successful.
706: */
707: function store($filename, $xacl, $oldxacl)
708: {
709: $xacl = explode(' ', $xacl);
710: $oldxacl = explode(' ', $oldxacl);
711: $both = array_intersect($xacl, $oldxacl);
712:
713: /* Removed access rights */
714: foreach (array_diff($oldxacl, $both) as $uid) {
715: if (!empty($uid)) {
716: $result = $this->_remove($filename, $uid);
717: if (is_a($result, 'PEAR_Error')) {
718: return $result;
719: }
720: }
721: }
722:
723: /* Added access rights */
724: foreach (array_diff($xacl, $both) as $uid) {
725: if (!empty($uid)) {
726: $result = $this->_add($filename, $uid);
727: if (is_a($result, 'PEAR_Error')) {
728: return $result;
729: }
730: }
731: }
732:
733: return true;
734: }
735: }
736:
737: /**
738: * A representation of a cache file.
739: *
740: * Copyright 2004-2008 Klarälvdalens Datakonsult AB
741: *
742: * See the enclosed file COPYING for license information (LGPL). If you
743: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
744: *
745: * @author Gunnar Wrobel <p@rdus.de>
746: * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
747: * @package Kolab_FreeBusy
748: */
749: class Horde_Kolab_FreeBusy_Cache_File {
750:
751: /**
752: * The suffix of this cache file.
753: *
754: * @var string
755: */
756: var $_suffix = '';
757:
758: /**
759: * Name of the cache file.
760: *
761: * @var string
762: */
763: var $_filename;
764:
765: /**
766: * Full path to the cache file.
767: *
768: * @var string
769: */
770: var $_file;
771:
772: /**
773: * Cache file version.
774: *
775: * @var int
776: */
777: var $_version = 1;
778:
779: /**
780: * Construct the Horde_Kolab_FreeBusy_Cache_File instance.
781: *
782: * @param string $cache_dir The path to the cache direcory.
783: * @param string $filename The file name of the cache file.
784: * @param string $suffix The suffix of the cache file name.
785: */
786: function Horde_Kolab_FreeBusy_Cache_File($cache_dir, $filename, $suffix = null)
787: {
788: if (!empty($suffix)) {
789: $this->_suffix = $suffix;
790: }
791:
792: $this->_cache_dir = $cache_dir;
793: $this->_filename = $filename;
794: $this->_file = $this->_cache_dir . '/' . $this->_filename . '.' . $this->_suffix;
795: }
796:
797: /**
798: * Get the full path to the cache file.
799: *
800: * @return string The full path to the file.
801: */
802: function getFile()
803: {
804: return $this->_file;
805: }
806:
807: /**
808: * Clean the cache file contents.
809: *
810: * @return boolean|PEAR_Error True if successful.
811: */
812: function purge()
813: {
814: if (file_exists($this->_file)) {
815: $result = @unlink($this->_file);
816: if (!$result) {
817: return PEAR::raiseError(sprintf("Failed removing file %s",
818: $this->_file));
819: }
820: }
821: return true;
822: }
823:
824: /**
825: * Store data in the cache file.
826: *
827: * @param mixed $data A reference to the data object.
828: *
829: * @return boolean|PEAR_Error True if successful.
830: */
831: function store(&$data)
832: {
833: /* Create directories if missing */
834: $fbdirname = dirname($this->_file);
835: if (!is_dir($fbdirname)) {
836: $result = $this->_makeTree($fbdirname);
837: if (is_a($result, 'PEAR_Error')) {
838: return $result;
839: }
840: }
841:
842: /* Store the cache data */
843: $fh = fopen($this->_file, 'w');
844: if (!$fh) {
845: return PEAR::raiseError(sprintf("Failed creating cache file %s!",
846: $this->_file));
847: }
848: fwrite($fh, serialize(array('version' => $this->_version,
849: 'data' => $data)));
850: fclose($fh);
851: return true;
852: }
853:
854: /**
855: * Load data from the cache file.
856: *
857: * @return mixed|PEAR_Error The data retrieved from the cache file.
858: */
859: function &load()
860: {
861: $file = @file_get_contents($this->_file);
862: if ($file === false) {
863: return PEAR::raiseError(sprintf("%s failed reading cache file %s!",
864: get_class($this), $this->_file));
865: }
866: $cache = @unserialize($file);
867: if ($cache === false) {
868: return PEAR::raiseError(sprintf("%s failed to unserialize cache data from file %s!",
869: get_class($this), $this->_file));
870: }
871: if (!isset($cache['version'])) {
872: return PEAR::raiseError(sprintf("Cache file %s lacks version data!",
873: $this->_file));
874: }
875: $this->_version = $cache['version'];
876: if (!isset($cache['data'])) {
877: return PEAR::raiseError(sprintf("Cache file %s lacks data!",
878: $this->_file));
879: }
880: if ($cache['version'] != $this->_version) {
881: return PEAR::raiseError(sprintf("Cache file %s has version %s while %s is required!",
882: $this->_file, $cache['version'], $this->_version));
883: }
884: return $cache['data'];
885: }
886:
887: /**
888: * Generate a tree of directories.
889: *
890: * @param string $dirname The path to a directory that should exist.
891: *
892: * @return boolean|PEAR_Error True if successful.
893: */
894: function _maketree($dirname)
895: {
896: $base = substr($dirname, 0, strrpos($dirname, '/'));
897: $base = str_replace(".", "^", $base);
898: if (!empty($base) && !is_dir($base)) {
899: $result = $this->_maketree($base);
900: if (is_a($result, 'PEAR_Error')) {
901: return $result;
902: }
903: }
904: if (!file_exists($dirname)) {
905: $result = @mkdir($dirname, 0755);
906: if (!$result) {
907: return PEAR::raiseError(sprintf("Error creating directory %s", $dirname));
908: }
909: }
910: return true;
911: }
912: }
913:
914: /**
915: * A cache file for partial free/busy information.
916: *
917: * Copyright 2004-2008 Klarälvdalens Datakonsult AB
918: *
919: * See the enclosed file COPYING for license information (LGPL). If you
920: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
921: *
922: * @author Gunnar Wrobel <p@rdus.de>
923: * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
924: * @package Kolab_FreeBusy
925: */
926: class Horde_Kolab_FreeBusy_Cache_File_pvcal extends Horde_Kolab_FreeBusy_Cache_File {
927:
928: /**
929: * The suffix of this cache file.
930: *
931: * @var string
932: */
933: var $_suffix = 'pvc';
934:
935: /**
936: * Store partial free/busy infomation in the cache file.
937: *
938: * @param Horde_Icalendar $pvcal A reference to the data object.
939: *
940: * @return boolean|PEAR_Error True if successful.
941: */
942: function storePVcal(&$pvcal)
943: {
944: return $this->store($pvcal);
945: }
946:
947: /**
948: * Load partial free/busy data from the cache file.
949: *
950: * @param boolean $extended Should the extended information be retrieved?
951: *
952: * @return Horde_Icalendar|PEAR_Error The data retrieved from the cache file.
953: */
954: function &loadPVcal($extended)
955: {
956: $pvcal = $this->load();
957: if (is_a($pvcal, 'PEAR_Error')) {
958: return $pvcal;
959: }
960: if (!$extended) {
961: $components = &$pvcal->getComponents();
962: foreach ($components as $component) {
963: if ($component->getType() == 'vFreebusy') {
964: $component->_extraParams = array();
965: }
966: }
967: }
968: return $pvcal;
969: }
970:
971: /**
972: * Return the last modification date of the cache file.
973: *
974: * @return int The last modification date.
975: */
976: function getMtime()
977: {
978: return filemtime($this->_file);
979: }
980: }
981:
982: /**
983: * A cache file for complete free/busy information.
984: *
985: * Copyright 2004-2008 Klarälvdalens Datakonsult AB
986: *
987: * See the enclosed file COPYING for license information (LGPL). If you
988: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
989: *
990: * @author Gunnar Wrobel <p@rdus.de>
991: * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
992: * @package Kolab_FreeBusy
993: */
994: class Horde_Kolab_FreeBusy_Cache_File_vcal extends Horde_Kolab_FreeBusy_Cache_File {
995:
996: /**
997: * The suffix of this cache file.
998: *
999: * @var string
1000: */
1001: var $_suffix = 'vc';
1002:
1003: /**
1004: * Cache file version.
1005: *
1006: * @var int
1007: */
1008: var $_version = 2;
1009:
1010: /**
1011: * Cached data.
1012: *
1013: * @var array
1014: */
1015: var $_data;
1016:
1017: /**
1018: * Construct the Horde_Kolab_FreeBusy_Cache_File_vcal instance.
1019: *
1020: * @param string $cache_dir The path to the cache direcory.
1021: * @param string $filename The file name of the cache file.
1022: * @param boolean $extended Does the cache hold extended data?
1023: */
1024: function Horde_Kolab_FreeBusy_Cache_File_vcal($cache_dir, $filename, $extended)
1025: {
1026: $extension = empty($extended) ? 'vc' : 'xvc';
1027: parent::Horde_Kolab_FreeBusy_Cache_File($cache_dir, $filename, $extension);
1028: }
1029:
1030: /**
1031: * Store free/busy infomation in the cache file.
1032: *
1033: * @param Horde_Icalendar $vcal A reference to the data object.
1034: * @param array $mtimes A list of modification times for the
1035: * partial free/busy cache times.
1036: *
1037: * @return boolean|PEAR_Error True if successful.
1038: */
1039: function storeVcal(&$vcal, &$mtimes)
1040: {
1041: $data = array('vcal' => $vcal,
1042: 'mtimes' => $mtimes);
1043: return $this->store($data);
1044: }
1045:
1046: /**
1047: * Load the free/busy information from the cache.
1048: *
1049: * @return Horde_Icalendar|PEAR_Error The retrieved free/busy information.
1050: */
1051: function &loadVcal()
1052: {
1053: if ($this->_data) {
1054: return $this->_data;
1055: }
1056:
1057: $result = $this->load();
1058: if (is_a($result, 'PEAR_Error')) {
1059: return $result;
1060: }
1061:
1062: $this->_data = $result['vcal'];
1063:
1064: return $this->_data;
1065: }
1066:
1067: /**
1068: * Check if the cached free/busy expired.
1069: *
1070: * @param array $files A list of partial free/busy cache files.
1071: *
1072: * @return boolean|PEAR_Error True if the cache expired.
1073: */
1074: function expired($files)
1075: {
1076: $result = $this->load();
1077: if (is_a($result, 'PEAR_Error')) {
1078: return $result;
1079: }
1080:
1081: /* Check the cache version */
1082: if ($this->_version < 2) {
1083: return true;
1084: }
1085:
1086: $this->_data = $result['vcal'];
1087:
1088: /* Files changed? */
1089: $keys = array_keys($result['mtimes']);
1090: $changes = array_diff($keys, $files);
1091: if (count($keys) != count($files) || !empty($changes)) {
1092: return true;
1093: }
1094:
1095: /* Check the file mtimes */
1096: foreach ($files as $file) {
1097: if (filemtime($result['mtimes'][$file][0]) != $result['mtimes'][$file][1]) {
1098: return true;
1099: }
1100: }
1101:
1102: /* Older than three days? */
1103: $components = $this->_data->getComponents();
1104: foreach ($components as $component) {
1105: if ($component->getType() == 'vFreebusy') {
1106: $attr = $component->getAttribute('DTSTAMP');
1107: if (!empty($attr) && !is_a($attr, 'PEAR_Error')) {
1108: //Should be configurable
1109: if (time() - (int)$attr > 259200) {
1110: return true;
1111: }
1112: }
1113: }
1114: }
1115:
1116: return false;
1117: }
1118: }
1119:
1120: /**
1121: * A cache file for ACLs. This serves as a buffer between the DB based
1122: * ACL storage and is required to hold the old ACL list for updates to
1123: * the DB based cache.
1124: *
1125: * Copyright 2004-2008 Klarälvdalens Datakonsult AB
1126: *
1127: * See the enclosed file COPYING for license information (LGPL). If you
1128: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
1129: *
1130: * @author Gunnar Wrobel <p@rdus.de>
1131: * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
1132: * @package Kolab_FreeBusy
1133: */
1134: class Horde_Kolab_FreeBusy_Cache_File_acl extends Horde_Kolab_FreeBusy_Cache_File {
1135:
1136: /**
1137: * The suffix of this cache file.
1138: *
1139: * @var string
1140: */
1141: var $_suffix = 'acl';
1142:
1143: /**
1144: * Link to the ACL stored in a data base.
1145: *
1146: * @var Horde_Kolab_FreeBusy_Cache_DB
1147: */
1148: var $_acls;
1149:
1150: /**
1151: * Construct the Horde_Kolab_FreeBusy_Cache_File_acl instance.
1152: *
1153: * @param string $cache_dir The path to the cache direcory.
1154: * @param string $filename The file name of the cache file.
1155: */
1156: function Horde_Kolab_FreeBusy_Cache_File_acl($cache_dir, $filename)
1157: {
1158: $this->_acls = &Horde_Kolab_FreeBusy_Cache_DB::singleton('acl', $cache_dir);
1159: parent::Horde_Kolab_FreeBusy_Cache_File($cache_dir, $filename, 'acl');
1160: }
1161:
1162: /**
1163: * Clean the cache file contents.
1164: *
1165: * @return boolean|PEAR_Error True if successful.
1166: */
1167: function purge()
1168: {
1169: $oldacl = $this->load();
1170: if (is_a($oldacl, 'PEAR_Error')) {
1171: $oldacl = array();
1172: }
1173:
1174: $result = $this->_acls->store($this->_filename, array(), $oldacl, false);
1175: if (is_a($result, 'PEAR_Error')) {
1176: return $result;
1177: }
1178:
1179: return parent::purge();
1180: }
1181:
1182: /**
1183: * Store a new ACL.
1184: *
1185: * @param array $acl The new ACL.
1186: * @param string $relevance Folder relevance.
1187: * @param string $append Should old entries be purged?
1188: *
1189: * @return boolean|PEAR_Error True if successful.
1190: */
1191: function storeACL(&$acl, $relevance, $append = false)
1192: {
1193: if (!$append) {
1194: $oldacl = $this->load();
1195: if (is_a($oldacl, 'PEAR_Error')) {
1196: $oldacl = array();
1197: }
1198: $acl = array_merge($oldacl, $acl);
1199: } else {
1200: $oldacl = array();
1201: }
1202:
1203: /* Handle relevance */
1204: switch ($relevance) {
1205: case 'readers':
1206: $perm = 'r';
1207: break;
1208: case 'nobody':
1209: $perm = false;
1210: break;
1211: case 'admins':
1212: default:
1213: $perm = 'a';
1214: }
1215:
1216: $result = $this->_acls->store($this->_filename, $acl, $oldacl, $perm);
1217: if (is_a($result, 'PEAR_Error')) {
1218: return $result;
1219: }
1220:
1221: $result = $this->store($acl);
1222: if (is_a($result, 'PEAR_Error')) {
1223: return $result;
1224: }
1225: return true;
1226: }
1227: }
1228:
1229: /**
1230: * A cache file for extended ACLs. This serves as a buffer between the
1231: * DB based ACL storage and is required to hold the old ACL list for
1232: * updates to the DB based cache.
1233: *
1234: * Copyright 2004-2008 Klarälvdalens Datakonsult AB
1235: *
1236: * See the enclosed file COPYING for license information (LGPL). If you
1237: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
1238: *
1239: * @author Gunnar Wrobel <p@rdus.de>
1240: * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
1241: * @package Kolab_FreeBusy
1242: */
1243: class Horde_Kolab_FreeBusy_Cache_File_xacl extends Horde_Kolab_FreeBusy_Cache_File {
1244:
1245: /**
1246: * The suffix of this cache file.
1247: *
1248: * @var string
1249: */
1250: var $_suffix = 'xacl';
1251:
1252: /**
1253: * Link to the ACL stored in a data base.
1254: *
1255: * @var Horde_Kolab_FreeBusy_Cache_DB
1256: */
1257: var $_xacls;
1258:
1259: /**
1260: * Construct the Horde_Kolab_FreeBusy_Cache_File_xacl instance.
1261: *
1262: * @param string $cache_dir The path to the cache direcory.
1263: * @param string $filename The file name of the cache file.
1264: */
1265: function Horde_Kolab_FreeBusy_Cache_File_xacl($cache_dir, $filename)
1266: {
1267: $this->_xacls = &Horde_Kolab_FreeBusy_Cache_DB::singleton('xacl', $cache_dir);
1268: parent::Horde_Kolab_FreeBusy_Cache_File($cache_dir, $filename, 'xacl');
1269: }
1270:
1271: /**
1272: * Clean the cache file contents.
1273: *
1274: * @return boolean|PEAR_Error True if successful.
1275: */
1276: function purge()
1277: {
1278: $oldxacl = $this->load();
1279: if (is_a($oldxacl, 'PEAR_Error')) {
1280: $oldxacl = '';
1281: }
1282:
1283: $result = $this->_xacls->store($this->_filename, '', $oldxacl);
1284: if (is_a($result, 'PEAR_Error')) {
1285: return $result;
1286: }
1287:
1288: return parent::purge();
1289: }
1290:
1291: /**
1292: * Store a new extended ACL.
1293: *
1294: * @param array $xacl The new extended ACL.
1295: * @param array $acl General ACL for the folder.
1296: *
1297: * @return boolean|PEAR_Error True if successful.
1298: */
1299: function storeXACL(&$xacl, &$acl)
1300: {
1301: $oldxacl = $this->load();
1302: if (is_a($oldxacl, 'PEAR_Error')) {
1303: $oldxacl = '';
1304: }
1305:
1306: /* Users with read access to the folder may also access the extended information */
1307: foreach ($acl as $user => $ac) {
1308: if (strpos($ac, 'r') !== false) {
1309: if (!empty($user)) {
1310: $xacl .= ' ' . $user;
1311: }
1312: }
1313: }
1314:
1315: $result = $this->_xacls->store($this->_filename, $xacl, $oldxacl);
1316: if (is_a($result, 'PEAR_Error')) {
1317: return $result;
1318: }
1319:
1320: $result = $this->store($xacl);
1321: if (is_a($result, 'PEAR_Error')) {
1322: return $result;
1323: }
1324: return true;
1325: }
1326: }
1327: