Overview

Packages

  • Kolab
    • Filter
    • Resource

Classes

  • Horde_Kolab_Resource_Epoch
  • Horde_Kolab_Resource_Freebusy
  • Horde_Kolab_Resource_Freebusy_Kolab
  • Horde_Kolab_Resource_Freebusy_Mock
  • Horde_Kolab_Resource_Reply
  • Kolab_Resource
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Resource management for the Kolab server.
  4:  *
  5:  * PHP version 5
  6:  *
  7:  * @category Kolab
  8:  * @package  Kolab_Filter
  9:  * @author   Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
 10:  * @author   Gunnar Wrobel <wrobel@pardus.de>
 11:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 12:  * @link     http://pear.horde.org/index.php?package=Kolab_Server
 13:  */
 14: 
 15: /** Load the iCal handling */
 16: require_once 'Horde/Icalendar.php';
 17: 
 18: /** Load MIME handlers */
 19: require_once 'Horde/MIME.php';
 20: require_once 'Horde/MIME/Message.php';
 21: require_once 'Horde/MIME/Headers.php';
 22: require_once 'Horde/MIME/Part.php';
 23: require_once 'Horde/MIME/Structure.php';
 24: 
 25: /** Load Kolab_Resource elements */
 26: require_once 'Horde/Kolab/Resource/Epoch.php';
 27: require_once 'Horde/Kolab/Resource/Itip.php';
 28: require_once 'Horde/Kolab/Resource/Reply.php';
 29: require_once 'Horde/Kolab/Resource/Freebusy.php';
 30: 
 31: // What actions we can take when receiving an event request
 32: define('RM_ACT_ALWAYS_ACCEPT',              'ACT_ALWAYS_ACCEPT');
 33: define('RM_ACT_REJECT_IF_CONFLICTS',        'ACT_REJECT_IF_CONFLICTS');
 34: define('RM_ACT_MANUAL_IF_CONFLICTS',        'ACT_MANUAL_IF_CONFLICTS');
 35: define('RM_ACT_MANUAL',                     'ACT_MANUAL');
 36: define('RM_ACT_ALWAYS_REJECT',              'ACT_ALWAYS_REJECT');
 37: 
 38: // What possible ITIP notification we can send
 39: define('RM_ITIP_DECLINE',                   1);
 40: define('RM_ITIP_ACCEPT',                    2);
 41: define('RM_ITIP_TENTATIVE',                 3);
 42: 
 43: /**
 44:  * Provides Kolab resource handling
 45:  *
 46:  * Copyright 2004-2010 Klarälvdalens Datakonsult AB
 47:  *
 48:  * See the enclosed file COPYING for license information (LGPL). If you
 49:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 50:  *
 51:  * @package Kolab_Filter
 52:  * @author  Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
 53:  * @author  Gunnar Wrobel <wrobel@pardus.de>
 54:  */
 55: class Kolab_Resource
 56: {
 57:     /**
 58:      * Returns the resource policy applying for the given sender
 59:      *
 60:      * @param string $sender   The sender address
 61:      * @param string $resource The resource
 62:      *
 63:      * @return array|PEAR_Error An array with "cn", "home server" and the policy.
 64:      */
 65:     function _getResourceData($sender, $resource)
 66:     {
 67:         require_once 'Horde/Kolab/Server.php';
 68:         $db = Horde_Kolab_Server::singleton();
 69:         if ($db instanceOf PEAR_Error) {
 70:             $db->code = OUT_LOG | EX_SOFTWARE;
 71:             return $db;
 72:         }
 73: 
 74:         $dn = $db->uidForMail($resource, Horde_Kolab_Server_Object::RESULT_MANY);
 75:         if ($dn instanceOf PEAR_Error) {
 76:             $dn->code = OUT_LOG | EX_NOUSER;
 77:             return $dn;
 78:         }
 79:         if (is_array($dn)) {
 80:             if (count($dn) > 1) {
 81:                 Horde::logMessage(sprintf("%s objects returned for %s",
 82:                                           $count($dn), $resource), 'WARN');
 83:                 return false;
 84:             } else {
 85:                 $dn = $dn[0];
 86:             }
 87:         }
 88:         $user = $db->fetch($dn, 'Horde_Kolab_Server_Object_Kolab_User');
 89: 
 90:         $cn      = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_CN);
 91:         $id      = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_MAIL);
 92:         $hs      = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_HOMESERVER);
 93:         if (is_a($hs, 'PEAR_Error')) {
 94:             return $hs;
 95:         }
 96:         $hs      = strtolower($hs);
 97:         $actions = $user->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_IPOLICY, false);
 98:         if (is_a($actions, 'PEAR_Error')) {
 99:             $actions->code = OUT_LOG | EX_UNAVAILABLE;
100:             return $actions;
101:         }
102:         if ($actions === false) {
103:             $actions = array(RM_ACT_MANUAL);
104:         }
105: 
106:         $policies = array();
107:         $defaultpolicy = false;
108:         foreach ($actions as $action) {
109:             if (preg_match('/(.*):(.*)/', $action, $regs)) {
110:                 $policies[strtolower($regs[1])] = $regs[2];
111:             } else {
112:                 $defaultpolicy = $action;
113:             }
114:         }
115:         // Find sender's policy
116:         if (array_key_exists($sender, $policies)) {
117:             // We have an exact match, stop processing
118:             $action = $policies[$sender];
119:         } else {
120:             $action = false;
121:             $dn = $db->uidForMailOrAlias($sender);
122:             if (is_a($dn, 'PEAR_Error')) {
123:                 $dn->code = OUT_LOG | EX_NOUSER;
124:                 return $dn;
125:             }
126:             if ($dn) {
127:                 // Sender is local, check for groups
128:                 foreach ($policies as $gid => $policy) {
129:                     if ($db->memberOfGroupAddress($dn, $gid)) {
130:                         // User is member of group
131:                         if (!$action) {
132:                             $action = $policy;
133:                         } else {
134:                             $action = min($action, $policy);
135:                         }
136:                     }
137:                 }
138:             }
139:             if (!$action && $defaultpolicy) {
140:                 $action = $defaultpolicy;
141:             }
142:         }
143:         return array('cn' => $cn, 'id' => $id,
144:                      'homeserver' => $hs, 'action' => $action);
145:     }
146: 
147:     function &_getICal($filename)
148:     {
149:         $requestText = '';
150:         $handle = fopen($filename, 'r');
151:         while (!feof($handle)) {
152:             $requestText .= fread($handle, 8192);
153:         }
154: 
155:         $mime = &MIME_Structure::parseTextMIMEMessage($requestText);
156: 
157:         $parts = $mime->contentTypeMap();
158:         foreach ($parts as $mimeid => $conttype) {
159:             if ($conttype == 'text/calendar') {
160:                 $part = $mime->getPart($mimeid);
161: 
162:                 $iCalendar = new Horde_Icalendar();
163:                 $iCalendar->parsevCalendar($part->transferDecode());
164: 
165:                 return $iCalendar;
166:             }
167:         }
168:         // No iCal found
169:         return false;
170:     }
171: 
172:     function _imapConnect($id)
173:     {
174:         global $conf;
175: 
176:         // Handle virtual domains
177:         list($user, $domain) = explode('@', $id);
178:         if (empty($domain)) {
179:             $domain = $conf['kolab']['filter']['email_domain'];
180:         }
181:         $calendar_user = $conf['kolab']['filter']['calendar_id'] . '@' . $domain;
182: 
183:         /* Load the authentication libraries */
184:         $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create(isset($conf['auth']['driver']) ? null : 'kolab');
185:         $authenticated = $auth->authenticate($calendar_user,
186:                                              array('password' => $conf['kolab']['filter']['calendar_pass']),
187:                                              false);
188: 
189:         if (is_a($authenticated, 'PEAR_Error')) {
190:             $authenticated->code = OUT_LOG | EX_UNAVAILABLE;
191:             return $authenticated;
192:         }
193:         if (!$authenticated) {
194:             return PEAR::raiseError(sprintf('Failed to authenticate as calendar user: %s',
195:                                             $auth->getLogoutReasonString()),
196:                                     OUT_LOG | EX_UNAVAILABLE);
197:         }
198:         @session_start();
199: 
200:         $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
201: 
202:         $_SESSION['__auth'] = array(
203:             'authenticated' => true,
204:             'userId' => $calendar_user,
205:             'timestamp' => time(),
206:             'credentials' => $secret->write($secret->getKey('auth'),
207:                                             serialize(array('password' => $conf['kolab']['filter']['calendar_pass']))),
208:             'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null,
209:         );
210: 
211:         /* Kolab IMAP handling */
212:         require_once 'Horde/Kolab/Storage/List.php';
213:         $list = &Kolab_List::singleton();
214:         $default = $list->getForeignDefault($id, 'event');
215:         if (!$default || is_a($default, 'PEAR_Error')) {
216:             $default = new Kolab_Folder();
217:             $default->setList($list);
218:             $default->setName($conf['kolab']['filter']['calendar_store']);
219:             //FIXME: The calendar user needs access here
220:             $attributes = array('default' => true,
221:                                 'type' => 'event',
222:                                 'owner' => $id);
223:             $result = $default->save($attributes);
224:             if (is_a($result, 'PEAR_Error')) {
225:                 $result->code = OUT_LOG | EX_UNAVAILABLE;
226:                 return $result;
227:             }
228:         }
229:         return $default;
230:     }
231: 
232:     function handleMessage($fqhostname, $sender, $resource, $tmpfname)
233:     {
234:         global $conf;
235: 
236:         $rdata = $this->_getResourceData($sender, $resource);
237:         if (is_a($rdata, 'PEAR_Error')) {
238:             return $rdata;
239:         } else if ($rdata === false) {
240:             /* No data, probably not a local user */
241:             return true;
242:         } else if ($rdata['homeserver'] && $rdata['homeserver'] != $fqhostname) {
243:             /* Not the users homeserver, ignore */
244:             return true;
245:         }
246: 
247:         $cn = $rdata['cn'];
248:         $id = $rdata['id'];
249:         if (isset($rdata['action'])) {
250:             $action = $rdata['action'];
251:         } else {
252:             // Manual is the only safe default!
253:             $action = RM_ACT_MANUAL;
254:         }
255:         Horde::logMessage(sprintf('Action for %s is %s',
256:                                   $sender, $action), 'DEBUG');
257: 
258:         // Get out as early as possible if manual
259:         if ($action == RM_ACT_MANUAL) {
260:             Horde::logMessage(sprintf('Passing through message to %s', $id), 'INFO');
261:             return true;
262:         }
263: 
264:         /* Get the iCalendar data (i.e. the iTip request) */
265:         $iCalendar = &$this->_getICal($tmpfname);
266:         if ($iCalendar === false) {
267:             // No iCal in mail
268:             Horde::logMessage(sprintf('Could not parse iCalendar data, passing through to %s', $id), 'INFO');
269:             return true;
270:         }
271:         // Get the event details out of the iTip request
272:         $itip = &$iCalendar->findComponent('VEVENT');
273:         if ($itip === false) {
274:             Horde::logMessage(sprintf('No VEVENT found in iCalendar data, passing through to %s', $id), 'INFO');
275:             return true;
276:         }
277:         $itip = new Horde_Kolab_Resource_Itip($itip);
278: 
279:         // What is the request's method? i.e. should we create a new event/cancel an
280:         // existing event, etc.
281:         $method = strtoupper(
282:             $iCalendar->getAttributeDefault(
283:                 'METHOD',
284:                 $itip->getMethod()
285:             )
286:         );
287: 
288:         // What resource are we managing?
289:         Horde::logMessage(sprintf('Processing %s method for %s', $method, $id), 'DEBUG');
290: 
291:         // This is assumed to be constant across event creation/modification/deletipn
292:         $uid = $itip->getUid();
293:         Horde::logMessage(sprintf('Event has UID %s', $uid), 'DEBUG');
294: 
295:         // Who is the organiser?
296:         $organiser = $itip->getOrganizer();
297:         Horde::logMessage(sprintf('Request made by %s', $organiser), 'DEBUG');
298: 
299:         // What is the events summary?
300:         $summary = $itip->getSummary();
301: 
302:         $estart = new Horde_Kolab_Resource_Epoch($itip->getStart());
303:         $dtstart = $estart->getEpoch();
304:         $eend = new Horde_Kolab_Resource_Epoch($itip->getEnd());
305:         $dtend = $eend->getEpoch();
306: 
307:         Horde::logMessage(sprintf('Event starts on <%s> %s and ends on <%s> %s.',
308:                                   $dtstart, $this->iCalDate2Kolab($dtstart), $dtend, $this->iCalDate2Kolab($dtend)), 'DEBUG');
309: 
310:         if ($action == RM_ACT_ALWAYS_REJECT) {
311:             if ($method == 'REQUEST') {
312:                 Horde::logMessage(sprintf('Rejecting %s method', $method), 'INFO');
313:                 return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_DECLINE,
314:                                             $organiser, $uid, $is_update);
315:             } else {
316:                 Horde::logMessage(sprintf('Passing through %s method for ACT_ALWAYS_REJECT policy', $method), 'INFO');
317:                 return true;
318:             }
319:         }
320: 
321:         $is_update  = false;
322:         $imap_error = false;
323:         $ignore     = array();
324: 
325:         $folder = $this->_imapConnect($id);
326:         if (is_a($folder, 'PEAR_Error')) {
327:             $imap_error = &$folder;
328:         }
329:         if (!is_a($imap_error, 'PEAR_Error') && !$folder->exists()) {
330:             $imap_error = &PEAR::raiseError('Error, could not open calendar folder!',
331:                                     OUT_LOG | EX_TEMPFAIL);
332:         }
333: 
334:         if (!is_a($imap_error, 'PEAR_Error')) {
335:             $data = $folder->getData();
336:             if (is_a($data, 'PEAR_Error')) {
337:                 $imap_error = &$data;
338:             }
339:         }
340: 
341:         if (is_a($imap_error, 'PEAR_Error')) {
342:             Horde::logMessage(sprintf('Failed accessing IMAP calendar: %s',
343:                                       $folder->getMessage()), 'ERR');
344:             if ($action == RM_ACT_MANUAL_IF_CONFLICTS) {
345:                 return true;
346:             }
347:         }
348: 
349:         switch ($method) {
350:         case 'REQUEST':
351:             if ($action == RM_ACT_MANUAL) {
352:                 Horde::logMessage(sprintf('Passing through %s method', $method), 'INFO');
353:                 break;
354:             }
355: 
356:             if (is_a($imap_error, 'PEAR_Error') || !$data->objectUidExists($uid)) {
357:                 $old_uid = null;
358:             } else {
359:                 $old_uid = $uid;
360:                 $ignore[] = $uid;
361:                 $is_update = true;
362:             }
363: 
364:             /** Generate the Kolab object */
365:             $object = $itip->getKolabObject();
366: 
367:             $outofperiod=0;
368: 
369:             // Don't even bother checking free/busy info if RM_ACT_ALWAYS_ACCEPT
370:             // is specified
371:             if ($action != RM_ACT_ALWAYS_ACCEPT) {
372: 
373:                 try {
374:                     require_once 'Horde/Kolab/Resource/Freebusy.php';
375:                     $fb  = Horde_Kolab_Resource_Freebusy::singleton();
376:                     $vfb = $fb->get($resource);
377:                 } catch (Exception $e) {
378:                     return PEAR::raiseError($e->getMessage(),
379:                                             OUT_LOG | EX_UNAVAILABLE);
380:                 }
381: 
382:                 $vfbstart = $vfb->getAttributeDefault('DTSTART', 0);
383:                 $vfbend = $vfb->getAttributeDefault('DTEND', 0);
384:                 Horde::logMessage(sprintf('Free/busy info starts on <%s> %s and ends on <%s> %s',
385:                                           $vfbstart, $this->iCalDate2Kolab($vfbstart), $vfbend, $this->iCalDate2Kolab($vfbend)), 'DEBUG');
386: 
387:                 $evfbend = new Horde_Kolab_Resource_Epoch($vfbend);
388:                 if ($vfbstart && $dtstart > $evfbend->getEpoch()) {
389:                     $outofperiod=1;
390:                 } else {
391:                     // Check whether we are busy or not
392:                     $busyperiods = $vfb->getBusyPeriods();
393:                     Horde::logMessage(sprintf('Busyperiods: %s',
394:                                               print_r($busyperiods, true)), 'DEBUG');
395:                     $extraparams = $vfb->getExtraParams();
396:                     Horde::logMessage(sprintf('Extraparams: %s',
397:                                               print_r($extraparams, true)), 'DEBUG');
398:                     $conflict = false;
399:                     if (!empty($object['recurrence'])) {
400:                         $recurrence = new Horde_Date_Recurrence($dtstart);
401:                         $recurrence->fromHash($object['recurrence']);
402:                         $duration = $dtend - $dtstart;
403:                         $events = array();
404:                         $next_start = $vfbstart;
405:                         $next = $recurrence->nextActiveRecurrence($vfbstart);
406:                         while ($next !== false && $next->compareDate($vfbend) <= 0) {
407:                             $next_ts = $next->timestamp();
408:                             $events[$next_ts] = $next_ts + $duration;
409:                             $next = $recurrence->nextActiveRecurrence(array('year' => $next->year,
410:                                                                             'month' => $next->month,
411:                                                                             'mday' => $next->mday + 1,
412:                                                                             'hour' => $next->hour,
413:                                                                             'min' => $next->min,
414:                                                                             'sec' => $next->sec));
415:                         }
416:                     } else {
417:                         $events = array($dtstart => $dtend);
418:                     }
419: 
420:                     foreach ($events as $dtstart => $dtend) {
421:                         Horde::logMessage(sprintf('Requested event from %s to %s',
422:                                                   strftime('%a, %d %b %Y %H:%M:%S %z', $dtstart),
423:                                                   strftime('%a, %d %b %Y %H:%M:%S %z', $dtend)
424:                                           ), 'DEBUG');
425:                         foreach ($busyperiods as $busyfrom => $busyto) {
426:                             if (empty($busyfrom) && empty($busyto)) {
427:                                 continue;
428:                             }
429:                             Horde::logMessage(sprintf('Busy period from %s to %s',
430:                                                       strftime('%a, %d %b %Y %H:%M:%S %z', $busyfrom),
431:                                                       strftime('%a, %d %b %Y %H:%M:%S %z', $busyto)
432:                                               ), 'DEBUG');
433:                             if ((isset($extraparams[$busyfrom]['X-UID'])
434:                                  && in_array(base64_decode($extraparams[$busyfrom]['X-UID']), $ignore))
435:                                 || (isset($extraparams[$busyfrom]['X-SID'])
436:                                     && in_array(base64_decode($extraparams[$busyfrom]['X-SID']), $ignore))) {
437:                                 // Ignore
438:                                 continue;
439:                             }
440:                             if (($busyfrom >= $dtstart && $busyfrom < $dtend) || ($dtstart >= $busyfrom && $dtstart < $busyto)) {
441:                                 Horde::logMessage('Request overlaps', 'DEBUG');
442:                                 $conflict = true;
443:                                 break;
444:                             }
445:                         }
446:                         if ($conflict) {
447:                             break;
448:                         }
449:                     }
450: 
451:                     if ($conflict) {
452:                         if ($action == RM_ACT_MANUAL_IF_CONFLICTS) {
453:                             //sendITipReply(RM_ITIP_TENTATIVE);
454:                             Horde::logMessage('Conflict detected; Passing mail through', 'INFO');
455:                             return true;
456:                         } else if ($action == RM_ACT_REJECT_IF_CONFLICTS) {
457:                             Horde::logMessage('Conflict detected; rejecting', 'INFO');
458:                             return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE,
459:                                                         $organiser, $uid, $is_update);
460:                         }
461:                     }
462:                 }
463:             }
464: 
465:             if (is_a($imap_error, 'PEAR_Error')) {
466:                 Horde::logMessage('Could not access users calendar; rejecting', 'INFO');
467:                 return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE,
468:                                             $organiser, $uid, $is_update);
469:             }
470: 
471:             // At this point there was either no conflict or RM_ACT_ALWAYS_ACCEPT
472:             // was specified; either way we add the new event & send an 'ACCEPT'
473:             // iTip reply
474: 
475:             Horde::logMessage(sprintf('Adding event %s', $uid), 'INFO');
476: 
477:             if (!empty($conf['kolab']['filter']['simple_locks'])) {
478:                 if (!empty($conf['kolab']['filter']['simple_locks_timeout'])) {
479:                     $timeout = $conf['kolab']['filter']['simple_locks_timeout'];
480:                 } else {
481:                     $timeout = 60;
482:                 }
483:                 if (!empty($conf['kolab']['filter']['simple_locks_dir'])) {
484:                     $lockdir = $conf['kolab']['filter']['simple_locks_dir'];
485:                 } else {
486:                     $lockdir = Horde::getTempDir() . '/Kolab_Filter_locks';
487:                     if (!is_dir($lockdir)) {
488:                         mkdir($lockdir, 0700);
489:                     }
490:                 }
491:                 if (is_dir($lockdir)) {
492:                     $lockfile = $lockdir . '/' . $resource . '.lock';
493:                     $counter = 0;
494:                     while ($counter < $timeout && file_exists($lockfile)) {
495:                         sleep(1);
496:                         $counter++;
497:                     }
498:                     if ($counter == $timeout) {
499:                         Horde::logMessage(sprintf('Lock timeout of %s seconds exceeded. Rejecting invitation.', $timeout), 'ERR');
500:                         return $this->sendITipReply($cn, $id, $itip, RM_ITIP_DECLINE,
501:                                                     $organiser, $uid, $is_update);
502:                     }
503:                     $result = file_put_contents($lockfile, 'LOCKED');
504:                     if ($result === false) {
505:                         Horde::logMessage(sprintf('Failed creating lock file %s.', $lockfile), 'ERR');
506:                     } else {
507:                         $this->lockfile = $lockfile;
508:                     }
509:                 } else {
510:                     Horde::logMessage(sprintf('The lock directory %s is missing. Disabled locking.', $lockdir), 'ERR');
511:                 }
512:             }
513: 
514:             $itip->setAccepted($resource);
515: 
516:             $result = $data->save($itip->getKolabObject(), $old_uid);
517:             if (is_a($result, 'PEAR_Error')) {
518:                 $result->code = OUT_LOG | EX_UNAVAILABLE;
519:                 return $result;
520:             }
521: 
522:             if ($outofperiod) {
523:                 Horde::logMessage('No freebusy information available', 'NOTICE');
524:                 return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_TENTATIVE,
525:                                             $organiser, $uid, $is_update);
526:             } else {
527:                 return $this->sendITipReply($cn, $resource, $itip, RM_ITIP_ACCEPT,
528:                                             $organiser, $uid, $is_update);
529:             }
530: 
531:         case 'CANCEL':
532:             Horde::logMessage(sprintf('Removing event %s', $uid), 'INFO');
533: 
534:             if (is_a($imap_error, 'PEAR_Error')) {
535:                 $body = sprintf(Horde_Kolab_Resource_Translation::t("Unable to access %s's calendar:"), $resource) . "\n\n" . $summary;
536:                 $subject = sprintf(Horde_Kolab_Resource_Translation::t("Error processing \"%s\""), $summary);
537:             } else if (!$data->objectUidExists($uid)) {
538:                 Horde::logMessage(sprintf('Canceled event %s is not present in %s\'s calendar',
539:                                           $uid, $resource), 'WARNING');
540:                 $body = sprintf(Horde_Kolab_Resource_Translation::t("The following event that was canceled is not present in %s's calendar:"), $resource) . "\n\n" . $summary;
541:                 $subject = sprintf(Horde_Kolab_Resource_Translation::t("Error processing \"%s\""), $summary);
542:             } else {
543:                 /**
544:                  * Delete the messages from IMAP
545:                  * Delete any old events that we updated
546:                  */
547:                 Horde::logMessage(sprintf('Deleting %s because of cancel',
548:                                           $uid), 'DEBUG');
549: 
550:                 $result = $data->delete($uid);
551:                 if (is_a($result, 'PEAR_Error')) {
552:                     Horde::logMessage(sprintf('Deleting %s failed with %s',
553:                                               $uid, $result->getMessage()), 'DEBUG');
554:                 }
555: 
556:                 $body = Horde_Kolab_Resource_Translation::t("The following event has been successfully removed:") . "\n\n" . $summary;
557:                 $subject = sprintf(Horde_Kolab_Resource_Translation::t("%s has been cancelled"), $summary);
558:             }
559: 
560:             Horde::logMessage(sprintf('Sending confirmation of cancelation to %s', $organiser), 'WARNING');
561: 
562:             $body = new MIME_Part('text/plain', Horde_String::wrap($body, 76));
563:             $mime = &MIME_Message::convertMimePart($body);
564:             $mime->setTransferEncoding('quoted-printable');
565:             $mime->transferEncodeContents();
566: 
567:             // Build the reply headers.
568:             $msg_headers = new MIME_Headers();
569:             $msg_headers->addHeader('Date', date('r'));
570:             $msg_headers->addHeader('From', $resource);
571:             $msg_headers->addHeader('To', $organiser);
572:             $msg_headers->addHeader('Subject', $subject);
573:             $msg_headers->addMIMEHeaders($mime);
574: 
575:             $reply = new Horde_Kolab_Resource_Reply(
576:                 $resource, $organiser, $msg_headers, $mime
577:             );
578:             Horde::logMessage('Successfully prepared cancellation reply', 'INFO');
579:             return $reply;
580: 
581:         default:
582:             // We either don't currently handle these iTip methods, or they do not
583:             // apply to what we're trying to accomplish here
584:             Horde::logMessage(sprintf('Ignoring %s method and passing message through to %s',
585:                                       $method, $resource), 'INFO');
586:             return true;
587:         }
588:     }
589: 
590:     /**
591:      * Helper function to clean up after handling an invitation
592:      *
593:      * @return NULL
594:      */
595:     function cleanup()
596:     {
597:         if (!empty($this->lockfile)) {
598:             @unlink($this->lockfile);
599:             if (file_exists($this->lockfile)) {
600:                 Horde::logMessage(sprintf('Failed removing the lockfile %s.', $lockfile), 'ERR');
601:             }
602:             $this->lockfile = null;
603:         }
604:     }
605: 
606:     /**
607:      * Send an automated reply.
608:      *
609:      * @param string  $cn                     Common name to be used in the iTip
610:      *                                        response.
611:      * @param string  $resource               Resource we send the reply for.
612:      * @param string  $Horde_Icalendar_Vevent The iTip information.
613:      * @param int     $type                   Type of response.
614:      * @param string  $organiser              The event organiser.
615:      * @param string  $uid                    The UID of the event.
616:      * @param boolean $is_update              Is this an event update?
617:      */
618:     function sendITipReply(
619:         $cn, $resource, $itip, $type, $organiser, $uid, $is_update, $comment = null
620:     ) {
621:         Horde::logMessage(sprintf('sendITipReply(%s, %s, %s, %s)',
622:                                   $cn, $resource, get_class($itip), $type),
623:                           'DEBUG');
624: 
625:         $itip_reply = new Horde_Kolab_Resource_Itip_Response(
626:             $itip,
627:             new Horde_Kolab_Resource_Itip_Resource_Base(
628:                 $resource, $cn
629:             )
630:         );
631:         switch($type) {
632:         case RM_ITIP_DECLINE:
633:             $type = new Horde_Kolab_Resource_Itip_Response_Type_Decline(
634:                 $resource, $itip
635:             );
636:             break;
637:         case RM_ITIP_ACCEPT:
638:             $type = new Horde_Kolab_Resource_Itip_Response_Type_Accept(
639:                 $resource, $itip
640:             );
641:             break;
642:         case RM_ITIP_TENTATIVE:
643:             $type = new Horde_Kolab_Resource_Itip_Response_Type_Tentative(
644:                 $resource, $itip
645:             );
646:             break;
647:         }
648:         list($headers, $message) = $itip_reply->getMessage(
649:             $type,
650:             '-//kolab.org//NONSGML Kolab Server 2//EN',
651:             $comment
652:         );
653: 
654:         Horde::logMessage(sprintf('Sending %s iTip reply to %s',
655:                                   $type->getStatus(),
656:                                   $organiser), 'DEBUG');
657: 
658:         $reply = new Horde_Kolab_Resource_Reply(
659:             $resource, $organiser, $headers, $message
660:         );
661:         Horde::logMessage('Successfully prepared iTip reply', 'DEBUG');
662:         return $reply;
663:     }
664: 
665:     /**
666:      * Clear information from a date array.
667:      *
668:      * @param array $ical_date  The array to clear.
669:      *
670:      * @return array The cleaned array.
671:      */
672:     function cleanArray($ical_date)
673:     {
674:         if (!array_key_exists('hour', $ical_date)) {
675:             $temp['DATE'] = '1';
676:         }
677:         $temp['hour']   = array_key_exists('hour', $ical_date) ? $ical_date['hour'] :  '00';
678:         $temp['minute']   = array_key_exists('minute', $ical_date) ? $ical_date['minute'] :  '00';
679:         $temp['second']   = array_key_exists('second', $ical_date) ? $ical_date['second'] :  '00';
680:         $temp['year']   = array_key_exists('year', $ical_date) ? $ical_date['year'] :  '0000';
681:         $temp['month']   = array_key_exists('month', $ical_date) ? $ical_date['month'] :  '00';
682:         $temp['mday']   = array_key_exists('mday', $ical_date) ? $ical_date['mday'] :  '00';
683:         $temp['zone']   = array_key_exists('zone', $ical_date) ? $ical_date['zone'] :  'UTC';
684: 
685:         return $temp;
686:     }
687: 
688:     /**
689:      * Conveert iCal dates to Kolab format.
690:      *
691:      * An all day event must have a dd--mm-yyyy notation and not a
692:      * yyyy-dd-mmT00:00:00z notation Otherwise the event is shown as a
693:      * 2-day event --> do not try to convert everything to epoch first
694:      *
695:      * @param array  $ical_date  The array to convert.
696:      * @param string $type       The type of the date to convert.
697:      *
698:      * @return string The converted date.
699:      */
700:     function iCalDate2Kolab($ical_date, $type= ' ')
701:     {
702:         Horde::logMessage(sprintf('Converting to kolab format %s',
703:                                   print_r($ical_date, true)), 'DEBUG');
704: 
705:         // $ical_date should be a timestamp
706:         if (is_array($ical_date)) {
707:             // going to create date again
708:             $temp = $this->cleanArray($ical_date);
709:             if (array_key_exists('DATE', $temp)) {
710:                 if ($type == 'ENDDATE') {
711:                     $etemp = new Horde_Kolab_Resource_Epoch($temp);
712:                     // substract a day (86400 seconds) using epochs to take number of days per month into account
713:                     $epoch= $etemp->getEpoch() - 86400;
714:                     $date = gmstrftime('%Y-%m-%d', $epoch);
715:                 } else {
716:                     $date= sprintf('%04d-%02d-%02d', $temp['year'], $temp['month'], $temp['mday']);
717:                 }
718:             } else {
719:                 $time = sprintf('%02d:%02d:%02d', $temp['hour'], $temp['minute'], $temp['second']);
720:                 if ($temp['zone'] == 'UTC') {
721:                     $time .= 'Z';
722:                 }
723:                 $date = sprintf('%04d-%02d-%02d', $temp['year'], $temp['month'], $temp['mday']) . 'T' . $time;
724:             }
725:         }  else {
726:             $date = gmstrftime('%Y-%m-%dT%H:%M:%SZ', $ical_date);
727:         }
728:         Horde::logMessage(sprintf('To <%s>', $date), 'DEBUG');
729:         return $date;
730:     }
731: }
732: 
API documentation generated by ApiGen