/** * Exports this task in iCalendar format. * * @param Horde_Icalendar $calendar A Horde_Icalendar object that acts as * the container. * * @return Horde_Icalendar_Vtodo A vtodo component of this task. */ public function toiCalendar(Horde_Icalendar $calendar) { $vTodo = Horde_Icalendar::newComponent('vtodo', $calendar); $v1 = $calendar->getAttribute('VERSION') == '1.0'; $vTodo->setAttribute('UID', $this->uid); if (!empty($this->assignee)) { $vTodo->setAttribute('ATTENDEE', Nag::getUserEmail($this->assignee), array('ROLE' => 'REQ-PARTICIPANT')); } $vTodo->setAttribute('ORGANIZER', !empty($this->organizer) ? Nag::getUserEmail($this->organizer) : Nag::getUserEmail($this->owner)); if (!empty($this->name)) { $vTodo->setAttribute('SUMMARY', $this->name); } if (!empty($this->desc)) { $vTodo->setAttribute('DESCRIPTION', $this->desc); } if (isset($this->priority)) { $vTodo->setAttribute('PRIORITY', $this->priority); } if (!empty($this->parent_id) && !empty($this->parent)) { $vTodo->setAttribute('RELATED-TO', $this->parent->uid); } if ($this->private) { $vTodo->setAttribute('CLASS', 'PRIVATE'); } if (!empty($this->start)) { $vTodo->setAttribute('DTSTART', $this->start); } if ($this->due) { $vTodo->setAttribute('DUE', $this->due); if ($this->alarm) { if ($v1) { $vTodo->setAttribute('AALARM', $this->due - $this->alarm * 60); } else { $vAlarm = Horde_Icalendar::newComponent('valarm', $vTodo); $vAlarm->setAttribute('ACTION', 'DISPLAY'); $vAlarm->setAttribute('TRIGGER;VALUE=DURATION', '-PT' . $this->alarm . 'M'); $vTodo->addComponent($vAlarm); } } } if ($this->completed) { $vTodo->setAttribute('STATUS', 'COMPLETED'); $vTodo->setAttribute('COMPLETED', $this->completed_date ? $this->completed_date : $_SERVER['REQUEST_TIME']); $vTodo->setAttribute('PERCENT-COMPLETE', '100'); } else { if (!empty($this->estimate)) { $vTodo->setAttribute('PERCENT-COMPLETE', $this->actual / $this->estimate * 100); } if ($v1) { $vTodo->setAttribute('STATUS', 'NEEDS ACTION'); } else { $vTodo->setAttribute('STATUS', 'NEEDS-ACTION'); } } // Recurrence. // We may have to implicitely set DTSTART if not set explicitely, may // some clients choke on missing DTSTART attributes while RRULE exists. if ($this->recurs()) { if ($v1) { $rrule = $this->recurrence->toRRule10($calendar); } else { $rrule = $this->recurrence->toRRule20($calendar); } if (!empty($rrule)) { $vTodo->setAttribute('RRULE', $rrule); } /* The completions represent deleted recurrences */ foreach ($this->recurrence->getCompletions() as $exception) { if (!empty($exception)) { // Use multiple EXDATE attributes instead of EXDATE // attributes with multiple values to make Apple iCal // happy. list($year, $month, $mday) = sscanf($exception, '%04d%02d%02d'); $vTodo->setAttribute('EXDATE', array(new Horde_Date($year, $month, $mday)), array('VALUE' => 'DATE')); } } } if ($this->tags) { $vTodo->setAttribute('CATEGORIES', implode(', ', $this->tags)); } /* Get the task's history. */ $created = $modified = null; try { $log = $GLOBALS['injector']->getInstance('Horde_History')->getHistory('nag:' . $this->tasklist . ':' . $this->uid); foreach ($log as $entry) { switch ($entry['action']) { case 'add': $created = $entry['ts']; break; case 'modify': $modified = $entry['ts']; break; } } } catch (Exception $e) { } if (!empty($created)) { $vTodo->setAttribute($v1 ? 'DCREATED' : 'CREATED', $created); if (empty($modified)) { $modified = $created; } } if (!empty($modified)) { $vTodo->setAttribute('LAST-MODIFIED', $modified); } return $vTodo; }
/** * Sends out iTip task notification to the assignee. * * Can be used to send task invitations, updates, and cancellations. * * @param Nag_Task $task The task in question. * @param Horde_Notification_Handler $notification * A notification object used to show result status. * @param integer $action * The type of notification to send. One of the Nag::ITIP_* values. * @param Horde_Date $instance * If cancelling a single instance of a recurring task, the date of * this instance. * @param string $range The range parameter if this is a recurring event. * Possible values are self::RANGE_THISANDFUTURE */ public static function sendITipNotifications(Nag_Task $task, Horde_Notification_Handler $notification, $action, Horde_Date $instance = null, $range = null) { global $injector, $registry, $nag_shares; if (!$task->assignee) { return; } $ident = $injector->getInstance('Horde_Core_Factory_Identity')->create($task->creator); if (!$ident->getValue('from_addr')) { $notification->push(sprintf(_("You do not have an email address configured in your Personal Information Preferences. You must set one %shere%s before event notifications can be sent."), $registry->getServiceLink('prefs', 'kronolith')->add(array('app' => 'horde', 'group' => 'identities'))->link(), '</a>'), 'horde.error', array('content.raw')); return; } // Generate image mime part first and only once, because we // need the Content-ID. $image = self::getImagePart('big_invitation.png'); $share = $nag_shares->getShare($task->tasklist); $view = new Horde_View(array('templatePath' => NAG_TEMPLATES . '/itip')); new Horde_View_Helper_Text($view); $view->identity = $ident; $view->task = $task; $view->imageId = $image->getContentId(); $email = Nag::getUserEmail($task->assignee); if (strpos($email, '@') === false) { continue; } /* Determine all notification-specific strings. */ $method = 'REQUEST'; switch ($action) { case self::ITIP_CANCEL: /* Cancellation. */ $method = 'CANCEL'; $filename = 'task-cancellation.ics'; $view->subject = sprintf(_("Cancelled: %s"), $task->name); if (empty($instance)) { $view->header = sprintf(_("%s has cancelled \"%s\"."), $ident->getName(), $task->name); } else { $view->header = sprintf(_("%s has cancelled an instance of the recurring \"%s\"."), $ident->getName(), $task->name); } break; case self::ITIP_UPDATE: if (!empty($task->organizer) && $task->organizer != Nag::getUserEmail($task->creator)) { // Sending a progress update. $method = 'REPLY'; } else { $method = 'UPDATE'; } case self::ITIP_REQUEST: default: if (empty($task->status) || $task->status == self::RESPONSE_NONE) { /* Invitation. */ $filename = 'task-invitation.ics'; $view->subject = $task->name; $view->header = sprintf(_("%s wishes to make you aware of \"%s\"."), $ident->getName(), $task->name); } else { $filename = 'task-update.ics'; $view->subject = sprintf(_("Updated: %s."), $task->name); $view->header = sprintf(_("%s wants to notify you about changes of \"%s\"."), $ident->getName(), $task->name); } break; } $view->attendees = $email; $view->organizer = empty($task->organizer) ? $registry->convertUserName($task->creator, false) : $task->organizer; /* Build the iCalendar data */ $iCal = new Horde_Icalendar(); $iCal->setAttribute('METHOD', $method); $vevent = $task->toiCalendar($iCal); $iCal->addComponent($vevent); /* text/calendar part */ $ics = new Horde_Mime_Part(); $ics->setType('text/calendar'); $ics->setContents($iCal->exportvCalendar()); $ics->setName($filename); $ics->setContentTypeParameter('METHOD', $method); $ics->setCharset('UTF-8'); $ics->setEOL("\r\n"); /* application/ics part */ $ics2 = clone $ics; $ics2->setType('application/ics'); /* multipart/mixed part */ $multipart = new Horde_Mime_Part(); $multipart->setType('multipart/mixed'); $inner = self::buildMimeMessage($view, 'notification', $image); $inner->addPart($ics); $multipart->addPart($inner); $multipart->addPart($ics2); $recipient = $method != 'REPLY' ? new Horde_Mail_Rfc822_Address($email) : new Horde_Mail_Rfc822_Address($task->organizer); $mail = new Horde_Mime_Mail(array('Subject' => $view->subject, 'To' => $recipient, 'From' => $ident->getDefaultFromAddress(true), 'User-Agent' => 'Nag ' . $registry->getVersion())); $mail->setBasePart($multipart); try { $mail->send($injector->getInstance('Horde_Mail')); $notification->push(sprintf(_("The task request notification to %s was successfully sent."), $recipient), 'horde.success'); } catch (Horde_Mime_Exception $e) { $notification->push(sprintf(_("There was an error sending a task request notification to %s: %s"), $recipient, $e->getMessage(), $e->getCode()), 'horde.error'); } }