<?php
/**
 * The IMP_MIME_Viewer_itip class displays vCalendar/iCalendar data
 * and provides an option to import the data into a calendar source,
 * if one is available.
 *
 * $Horde: imp/lib/MIME/Viewer/itip.php,v 1.37.2.3 2005/01/20 10:35:35 jan Exp $
 *
 * Copyright 2002-2005 Chuck Hagenbuch <chuck@horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @since   IMP 4.0
 * @package Horde_MIME_Viewer
 */
class IMP_MIME_Viewer_itip extends MIME_Viewer {

    /**
     * The messages to output to the user.
     * @var array $_msgs
     */
    var $_msgs = array();

    /**
     * The method as marked in either the iCal structure or message
     * header.
     * @var string $_method
     */
    var $_method = 'PUBLISH';

    /**
     * The headers of the message.
     * @var string $_headers
     */
    var $_headers;

    /**
     * Render out the currently set iCalendar contents.
     *
     * @access public
     *
     * @param optional array $params  Any parameters the Viewer may need.
     *
     * @return string  The rendered contents.
     */
    function render($params = array())
    {
        global $registry;
        require_once 'Horde/iCalendar.php';

        // Extract the data.
        $data = $this->mime_part->getContents();
        $this->_getHeaders($params[0]);

        // Parse the iCal file.
        $vCal = &new Horde_iCalendar();
        if (!$vCal->parsevCalendar($data)) {
            $html = _("<b>The calendar data is invalid</b>");
            $html .= '<br /><pre>' . htmlspecialchars($data) . '</pre>';
            return $html;
        }

        // Check if we got vcard data with the wrong vcalendar mime
        // type.
        $c = $vCal->getComponentClasses();
        if (count($c) == 1 && !empty($c['horde_icalendar_vcard'])) {
            $vcard_renderer = &Mime_Viewer::factory($this->mime_part, 'text/x-vcard');
            return $vcard_renderer->render($params);
        }

        // Get the method type.
        $this->_method = $vCal->getAttribute('METHOD');

        // Get the iCalendar file components.
        $components = $vCal->getComponents();

        // Handle the action requests.
        $actions = Util::getFormData('action', array());
        foreach ($actions as $key => $action) {
            switch ($action) {
            case 'delete':
                // vEvent cancellation.
                if ($registry->hasMethod('calendar/delete')) {
                    $guid = $components[$key]->getAttribute('UID');
                    $event = $registry->call('calendar/delete', array('guid' => $guid));
                    if (is_a($event, 'PEAR_Error')) {
                        $this->_msgs[$key] = array('error', sprintf(_("There was an error deleting the event: %s."), $event->getMessage()));
                    } else {
                        $this->_msgs[$key] = array('success', _("Event successfully deleted."));
                    }
                } else {
                    $this->_msgs[$key] = array('warning', _("This action is not supported."));
                }
                break;

            case 'update':
                // vEvent reply.
                if ($registry->hasMethod('calendar/updateAttendee')) {
                    $event = $registry->call('calendar/updateAttendee', array('response' => $components[$key]));
                    if (is_a($event, 'PEAR_Error')) {
                        $this->_msgs[$key] = array('error', sprintf(_("There was an error updating the event: %s."), $event->getMessage()));
                    } else {
                        $this->_msgs[$key] = array('success', _("Respondent Status Updated."));
                    }
                } else {
                    $this->_msgs[$key] = array('warning', _("This action is not supported."));
                }
                break;

            case 'import':
                // vFreebusy reply.
                // vFreebusy publish.
                // vEvent request.
                // vEvent publish.
                // vTodo publish.
                // vJournal publish.
                switch ($components[$key]->getType()) {
                case 'vEvent':
                    // Import into Kronolith.
                    if ($registry->hasMethod('calendar/import')) {
                        $guid = $registry->call('calendar/import', array('content' => $components[$key], 'contentType' => 'text/calendar'));
                        if (is_a($guid, 'PEAR_Error')) {
                            $this->_msgs[$key] = array('error', sprintf(_("There was an error importing the event: %s."), $guid->getMessage()));
                        } else {
                            $url = Horde::url($registry->link('calendar/show', array('uid' => $guid)));
                            $this->_msgs[$key] = array('success', _("The event was added to your calendar.") .
                                                       '&nbsp;' . Horde::link($url, _("View event"), null, '_blank') . Horde::img('mime/icalendar.png', _("View event"), null, $registry->getImageDir('horde')) . '</a>');
                        }
                    } else {
                        $this->_msgs[$key] = array('warning', _("This action is not supported."));
                    }
                    break;

                case 'vFreebusy':
                    // Import into Kronolith.
                    if ($registry->hasMethod('calendar/import_vfreebusy')) {
                        $res = $registry->call('calendar/import_vfreebusy', array($components[$key]));
                        if (is_a($res, 'PEAR_Error')) {
                            $this->_msgs[$key] = array('error', sprintf(_("There was an error importing user's free/busy information: %s."), $res->getMessage()));
                        } else {
                            $this->_msgs[$key] = array('success', _("The user's free/busy information was sucessfully stored."));
                        }
                    } else {
                        $this->_msgs[$key] = array('warning', _("This action is not supported."));
                    }
                    break;

                case 'vTodo':
                    // Import into Nag.
                    if ($registry->hasMethod('tasks/import')) {
                        $guid = $registry->call('tasks/import', array($components[$key], 'text/x-vtodo'));
                        if (is_a($guid, 'PEAR_Error')) {
                            $this->_msgs[$key] = array('error', sprintf(_("There was an error importing the task: %s."), $guid->getMessage()));
                        } else {
                            $url = Horde::url($registry->link('tasks/show', array('uid' => $guid)));
                            $this->_msgs[$key] = array('success', _("The task has been added to your tasklist.") .
                                                       '&nbsp;' . Horde::link($url, _("View task"), null, '_blank') . Horde::img('mime/icalendar.png', _("View task"), null, $registry->getImageDir('horde')) . '</a>');
                        }
                    } else {
                        $this->_msgs[$key] = array('warning', _("This action is not supported."));
                    }
                    break;

                case 'vJournal':
                default:
                    $this->_msgs[$key] = array('warning', _("This action is not yet implemented."));
                }

                break;

            case 'accept':
            case 'deny':
            case 'tentative':
                // vEvent request.
                if (isset($components[$key]) && $components[$key]->getType() == 'vEvent') {
                    $vEvent = $components[$key];

                    require_once 'Horde/Identity.php';
                    require_once 'Horde/MIME.php';
                    require_once 'Horde/MIME/Headers.php';
                    require_once 'Horde/MIME/Part.php';

                    // Get the organizer details.
                    $organizer = parse_url($vEvent->getAttribute('ORGANIZER'));
                    $organizerEmail = $organizer['path'];
                    $organizer = $vEvent->getAttribute('ORGANIZER', true);
                    $organizerName = isset($organizer['cn']) ? $organizer['cn'] : '';

                    // Build the reply.
                    $vCal = &new Horde_iCalendar();
                    $vCal->setAttribute('PRODID', '-//The Horde Project//' . HORDE_AGENT_HEADER . '//EN');
                    $vCal->setAttribute('METHOD', 'REPLY');

                    $vEvent_reply = &Horde_iCalendar::newComponent('vevent', $vCal);
                    $vEvent_reply->setAttribute('UID', $vEvent->getAttribute('UID'));
                    if (!is_a($vEvent->getAttribute('SUMMARY'), 'PEAR_error')) {
                        $vEvent_reply->setAttribute('SUMMARY', $vEvent->getAttribute('SUMMARY'));
                    }
                    if (!is_a($vEvent->getAttribute('DESCRIPTION'), 'PEAR_error')) {
                        $vEvent_reply->setAttribute('DESCRIPTION', $vEvent->getAttribute('DESCRIPTION'));
                    }
                    $vEvent_reply->setAttribute('DTSTART', $vEvent->getAttribute('DTSTART'), array_pop($vEvent->getAttribute('DTSTART', true)));
                    if (!is_a($vEvent->getAttribute('DTEND'), 'PEAR_error')) {
                        $vEvent_reply->setAttribute('DTEND', $vEvent->getAttribute('DTEND'), array_pop($vEvent->getAttribute('DTEND', true)));
                    } else {
                        $vEvent_reply->setAttribute('DURATION', $vEvent->getAttribute('DURATION'), array_pop($vEvent->getAttribute('DURATION', true)));
                    }
                    if (!is_a($vEvent->getAttribute('SEQUENCE'), 'PEAR_error')) {
                        $vEvent_reply->setAttribute('SEQUENCE', $vEvent->getAttribute('SEQUENCE') + 1);
                    } else {
                        $vEvent_reply->setAttribute('SEQUENCE', 0);
                    }
                    $vEvent_reply->setAttribute('ORGANIZER', $vEvent->getAttribute('ORGANIZER'), array_pop($vEvent->getAttribute('ORGANIZER', true)));

                    // Find out who we are and update status.
                    $identity = &Identity::singleton();
                    $email = $identity->getValue('from_addr');
                    $cn = $identity->getValue('fullname');
                    $params = array('CN' => $cn);

                    switch ($action) {
                    case 'accept':
                        $message = sprintf(_("%s has accepted."), $cn);
                        $subject = 'Accepted: ' . $vEvent->getAttribute('SUMMARY');
                        $params['PARTSTAT'] = 'ACCEPTED';
                        break;

                    case 'deny':
                        $message = sprintf(_("%s has declined."), $cn);
                        $subject = 'Declined: ' . $vEvent->getAttribute('SUMMARY');
                        $params['PARTSTAT'] = 'DECLINED';
                        break;

                    case 'tentative':
                        $message = sprintf(_("%s has tentatively accepted."), $cn);
                        $subject = 'Tentative: ' . $vEvent->getAttribute('SUMMARY');
                        $params['PARTSTAT'] = 'TENTATIVE';
                        break;
                    }

                    $vEvent_reply->setAttribute('ATTENDEE', 'MAILTO:' . $email, $params);
                    $vCal->addComponent($vEvent_reply);

                    $mime = &new MIME_Part('multipart/alternative');
                    $body = &new MIME_Part('text/plain', String::wrap($message, 76, "\n"));

                    $ics = &new MIME_Part('text/calendar', $vCal->exportvCalendar());
                    $ics->setName('event-reply.ics');
                    $ics->setContentTypeParameter('METHOD', 'REPLY');

                    $mime->addPart($body);
                    $mime->addPart($ics);
                    $mime = &MIME_Message::convertMimePart($mime);

                    // Build the reply headers.
                    $msg_headers = &new MIME_Headers();
                    $msg_headers->addReceivedHeader();
                    $msg_headers->addMessageIdHeader();
                    $msg_headers->addHeader('Date', date('r'));
                    $msg_headers->addHeader('From', $email);
                    $msg_headers->addHeader('To', $organizerEmail);

                    $identity->setDefault(Util::getFormData('identity'));
                    $replyto = $identity->getValue('replyto_addr');
                    if (!empty($replyto) && ($replyto != $email)) {
                        $msg_headers->addHeader('Reply-to', $replyto);
                    }
                    $msg_headers->addHeader('Subject', $subject);
                    $msg_headers->addMIMEHeaders($mime);

                    // Send the reply.
                    $status = $mime->send($organizerEmail, $msg_headers);
                    if (is_a($status, 'PEAR_Error')) {
                        $this->_msgs[$key] = array('error', sprintf(_("Error sending reply: %s."), $status->getMessage()));
                    } else {
                        $this->_msgs[$key] = array('success', _("Reply Sent."));
                    }
                } else {
                    $this->_msgs[$key] = array('warning', _("This action is not supported."));
                }
                break;

            case 'send':
                // vEvent refresh.
                if (isset($components[$key]) && $components[$key]->getType() == 'vEvent') {
                    $vEvent = $components[$key];
                }

                // vTodo refresh.
            case 'reply':
            case 'reply2m':
                // vfreebusy request.
                if (isset($components[$key]) && $components[$key]->getType() == 'vFreebusy') {
                    $vFb = $components[$key];
                    if ($action == 'reply2m') {
                        $startStamp = time();
                        $endStamp = $startStamp + (60 * 24 * 3600);
                    } else {
                        $startStamp = $vFb->getAttribute('DTSTART');
                        if (is_a($startStamp, 'PEAR_Error')) {
                            $startStamp = time();
                        }
                        $endStamp = $vFb->getAttribute('DTEND');
                        if (is_a($endStamp, 'PEAR_Error')) {
                            $duration = $vFb->getAttribute('DURATION');
                            if (is_a($duration, 'PEAR_Error')) {
                                $endStamp = $startStamp + (60 * 24 * 3600);
                            } else {
                                $endStamp = $startStamp + $duration;
                            }
                        }
                    }
                    $vfb_reply = $registry->call('calendar/getFreeBusy', array('startStamp' => $startStamp,
                                                                               'endStamp' => $endStamp));
                    require_once 'Horde/Identity.php';
                    require_once 'Horde/MIME.php';
                    require_once 'Horde/MIME/Headers.php';
                    require_once 'Horde/MIME/Part.php';

                    // Find out who we are and update status.
                    $identity = &Identity::singleton();
                    $email = $identity->getValue('from_addr');
                    $cn = $identity->getValue('fullname');

                    // Get the organizer details.
                    $organizer = parse_url($vFb->getAttribute('ORGANIZER'));
                    $organizerEmail = $organizer['path'];
                    $organizer = $vFb->getAttribute('ORGANIZER', true);
                    $organizerName = isset($organizer['cn']) ? $organizer['cn'] : '';

                    // Build the reply.
                    $vCal = &new Horde_iCalendar();
                    $vCal->setAttribute('PRODID', '-//The Horde Project//' . HORDE_AGENT_HEADER . '//EN');
                    $vCal->setAttribute('METHOD', 'REPLY');
                    $vCal->addComponent($vfb_reply);

                    $mime = &new MIME_Message();
                    $message = _("Attached is a reply to a calendar request you sent.");
                    $body = &new MIME_Part('text/plain', String::wrap($message, 76, "\n"));

                    $ics = &new MIME_Part('text/calendar', $vCal->exportvCalendar());
                    $ics->setName('icalendar.ics');
                    $ics->setContentTypeParameter('METHOD', 'REPLY');

                    $mime->addPart($body);
                    $mime->addPart($ics);

                    // Build the reply headers.
                    $msg_headers = &new MIME_Headers();
                    $msg_headers->addReceivedHeader();
                    $msg_headers->addMessageIdHeader();
                    $msg_headers->addHeader('Date', date('r'));
                    $msg_headers->addHeader('From', $email);
                    $msg_headers->addHeader('To', $organizerEmail);

                    $identity->setDefault(Util::getFormData('identity'));
                    $replyto = $identity->getValue('replyto_addr');
                    if (!empty($replyto) && ($replyto != $email)) {
                        $msg_headers->addHeader('Reply-to', $replyto);
                    }
                    $msg_headers->addHeader('Subject', _("Free/Busy Request Response"));
                    $msg_headers->addMIMEHeaders($mime);

                    // Send the reply.
                    $status = $mime->send($organizerEmail, $msg_headers);
                    if (is_a($status, 'PEAR_Error')) {
                        $this->_msgs[$key] = array('error', sprintf(_("Error sending reply: %s."), $status->getMessage()));
                    } else {
                        $this->_msgs[$key] = array('success', _("Reply Sent."));
                    }
                } else {
                    $this->_msgs[$key] = array('warning', _("Invalid Action selected for this component."));
                }
                break;

            case 'nosup':
                // vFreebusy request.
            default:
                $this->_msgs[$key] = array('warning', _("This action is not yet implemented."));
                break;
            }
        }

        // Create the HTML to display the iCal file. Need to work out
        // if we are inline and actually need this.
        $html = Util::bufferOutput('require', $registry->get('templates', 'horde') . '/common-header.inc');
        $html .= '<form method="post" name="iCal" action="' . Horde::selfURL(true) . '">';

        foreach ($components as $key => $component) {
            switch ($component->getType()) {
            case 'vEvent':
                $html .= $this->_vEvent($component, $key);
                break;

            case 'vTimeZone':
                // Ignore them.
                break;

            case 'vFreebusy':
                $html .= $this->_vFreebusy($component, $key);
                break;

            // @todo: handle stray vcards here as well.
            default:
                $html .= sprintf(_("Unhandled component of type: %s"), $component->getType());
            }
        }

        // Need to work out if we are inline and acutally need this.
        $html .= '</form>';
        $html .= Util::bufferOutput('require', $registry->get('templates', 'horde') . '/common-footer.inc');

        return $html;
    }

    function _row($label, $value)
    {
        if (substr($label, 0, 2) == 'DT') {
            $value = strftime("%x %X", $value);
        }
        return '<tr><td valign="top" class="item">' . $label . '</td><td valign="top" class="item">' . $value . "</td></tr>\n";
    }

    /**
     * Return the html for a vFreebusy.
     */
    function _vFreebusy($vfb, $id)
    {
        global $registry, $prefs;

        $html = '<table cellspacing="1">';

        $desc = '';
        $title = '';
        $sender = $vfb->getName();
        switch ($this->_method) {
        case 'PUBLISH':
            $desc = _("%s has sent you free/busy information.");
            $title = _("Free/Busy Information");
            break;

        case 'REQUEST':
            $desc = _("%s requests your free/busy information.");
            $title = _("Free/Busy Request");
            $sender = $this->_headers->getValue('From');
            break;

        case 'REPLY':
            $desc = _("%s has replied to a free/busy request.");
            $title = _("Free/Busy Reply");
            break;
        }

        $desc = sprintf($desc, $sender);

        $html .= '<tr><td colspan="2" class="header">' . $title . '</td></tr>';
        $html .= '<tr><td colspan="2" class="control">' . $desc . '<br/>';
        $html .= _("Please select an action from the menu below.") . '</td></tr>';

        if (isset($this->_msgs[$id])) {
            $html .= '<tr><td colspan="2" class="notice"><b>' . Horde::img('alerts/' . $this->_msgs[$id][0] . '.png', '', null, $registry->getImageDir('horde')) . $this->_msgs[$id][1] . '</b></td></tr>';
        }

        $start = $vfb->getAttribute('DTSTART');
        if (!is_a($start, 'PEAR_Error')) {
            $html .= sprintf('<tr><td colspan="2" class="item"><b>%s:</b>&nbsp;%s</td></tr>', _("Start"), strftime($prefs->getValue('date_format') . ' ' . $prefs->getValue('time_format'), $start));
        }

        $end = $vfb->getAttribute('DTEND');
        if (!is_a($end, 'PEAR_Error')) {
            $html .= sprintf('<tr><td colspan="2" class="item"><b>%s:</b>&nbsp;%s</td></tr>', _("End"), strftime($prefs->getValue('date_format') . ' ' . $prefs->getValue('time_format'), $end));
        }

        $html .= '<tr><td colspan="2" class="control"><b>' . _("Actions") . ":</b><br/>";
        $html .= _("Choose an action:") . "&nbsp;<select name='action[$id]'>";

        switch ($this->_method) {
        case 'PUBLISH':
            if ($registry->hasMethod('calendar/import_vfreebusy')) {
                $html .= '<option value="import">' .   _("Remember the free/busy information.") . "</option>";
            } else {
                $html .= '<option value="nosup">' . _("Reply with Not Supported Message") . "</option>";
            }
            break;

        case 'REQUEST':
            if ($registry->hasMethod('calendar/getFreeBusy')) {
                $html .= '<option value="reply">' .   _("Reply with requested free/busy information.") . "</option>";
                $html .= '<option value="reply2m">' . _("Reply with free/busy for next 2 months.") . "</option>";
            } else {
                $html .= '<option value="nosup">' . _("Reply with Not Supported Message") . "</option>";
            }

            $html .= '<option value="deny">' . _("Deny request for free/busy information") . "</option>";
            break;

        case 'REPLY':
            if ($registry->hasMethod('calendar/import_vfreebusy')) {
                $html .= '<option value="import">' .   _("Remember the free/busy information.") . "</option>";
            } else {
                $html .= '<option value="nosup">' . _("Reply with Not Supported Message") . "</option>";
            }
            break;
        }

        $html .= sprintf('</select>&nbsp<input type="submit" class="button" value="%s" /><br />', _("OK"));
        $html .= "</td></tr>";
        $html .= '</table>';
        return $html;
    }

    /**
     * Return the html for a vEvent.
     */
    function _vEvent($vevent, $id)
    {
        global $registry, $prefs;

        $html = '<table cellspacing="1">';

        $desc = '';
        $title = '';
        $sender = $vevent->organizerName();
        $options = array();

        switch ($this->_method) {
        case 'PUBLISH':
            $desc = _("%s wishes to make you aware of %s.");
            $title = _("Meeting Information");
            if ($registry->hasMethod('calendar/import')) {
                $options[] = '<option value="import">' .   _("Add this to my calendar") . "</option>";
            }

            break;

        case 'REQUEST':
            // Check that you are one of the attendees here.
            $desc = _("%s requests your presence at %s.");
            $title = _("Meeting Proposal");
            if ($registry->hasMethod('calendar/import')) {
                $options[] = '<option value="import">' .   _("Add this to my calendar") . "</option>";
            }
            $options[] = '<option value="accept">' . _("Accept request") . "</option>";
            $options[] = '<option value="tentative">' . _("Tentatively Accept request") . "</option>";
            $options[] = '<option value="deny">' . _("Deny request") . "</option>";
            // $options[] = '<option value="delegate">' . _("Delegate position") . '</option>';
            break;

        case 'ADD':
            $desc = _("%s wishes to ammend %s.");
            $title = _("Meeting Update");
            break;

        case 'REFRESH':
            $desc = _("%s wishes to receive the latest information about %s.");
            $title = _("Meeting Update Request");
            $options[] = '<option value="send">' . _("Send Latest Information") . '</option>';
            // Fetch the event info from the UID here so more info can
            // be displayed.
            $found = false;
            if ($registry->hasMethod('meeting/vevent_from_uid')) {
                $existing_vevent = $registry->call('meeting/vevent_from_uid', array('uid' => $vevent->getAttribute('UID')));
                if (!is_a($existing_vevent, 'Pear_error')) {
                    $existing_vevent->updateFromvEvent($vevent);
                    $vevent = $existing_vevent;
                    $found = true;
                }
            }
            if (!$found && $registry->hasMethod('calendar/eventFromGUID')) {
                $existing_vevent = $registry->call('calendar/eventFromGUID', array('guid' => $vevent->getAttribute('UID')));
                if (!is_a($existing_vevent, 'Pear_error')) {
                    $existing_vevent->updateFromvEvent($vevent);
                    $vevent = $existing_vevent;
                    $found = true;
                }
            }
            break;

        case 'REPLY':
            $desc = _("%s has replied to the invitation to %s.");
            $title = _("Meeting Reply");
            $sender = $this->_headers->getValue('From');
            if ($registry->hasMethod('calendar/updateAttendee')) {
                $options[] = '<option value="update">' . _("Update respondent status") . '</option>';
            }
            // Fetch the event info from the UID here so more info can
            // be displayed.
            $found = false;
            if ($registry->hasMethod('meeting/vevent_from_uid')) {
                $existing_vevent = $registry->call('meeting/vevent_from_uid', array('uid' => $vevent->getAttribute('UID')));
                if (!is_a($existing_vevent, 'Pear_error')) {
                    $existing_vevent->updateAttendeesFromvEvent($vevent);
                    $vevent = $existing_vevent;
                    $found = true;
                }
            }
            break;

        case 'CANCEL':
            $desc = _("%s has cancelled %s.");
            $title = _("Meeting Cancellation");
            if ($registry->hasMethod('calendar/delete')) {
                $options[] = '<option value="delete">' . _("Delete from my calendar") . '</option>';
            }
            // Fetch the event info from the UID here so more info can
            // be displayed.
            $found = false;
            if ($registry->hasMethod('meeting/vevent_from_uid')) {
                $existing_vevent = $registry->call('meeting/vevent_from_uid', array('uid' => $vevent->getAttribute('UID')));
                if (!is_a($existing_vevent, 'Pear_error')) {
                    $existing_vevent->updateFromvEvent($vevent);
                    $vevent = $existing_vevent;
                    $found = true;
                }
            }
            break;
        }

        $summary = $vevent->getAttribute('SUMMARY');
        if (is_a($summary, 'PEAR_Error')) {
            $desc = sprintf($desc, $sender, _("Unknown Meeting"));
        } else {
            $desc = sprintf($desc, $sender, $summary);
        }

        $html .= '<tr><td colspan="2" class="header">' . $title . '</td></tr>';
        $html .= '<tr><td colspan="2" class="control">' . $desc . '<br/>';
        $html .= _("Please review the following information, and then select an action from the menu below.") . '</td></tr>';

        if (isset($this->_msgs[$id])) {
            $html .= '<tr><td colspan="2" class="notice"><b>' . Horde::img('alerts/' . $this->_msgs[$id][0] . '.png', '', null, $registry->getImageDir('horde')) . $this->_msgs[$id][1] . '</b></td></tr>';
        }

        $start = $vevent->getAttribute('DTSTART');
        if (!is_a($start, 'PEAR_Error')) {
            $html .= sprintf('<tr><td colspan="2" class="item"><b>%s:</b>&nbsp;%s</td></tr>', _("Start"), strftime($prefs->getValue('date_format') . ' ' . $prefs->getValue('time_format'), $start));
        }

        $end = $vevent->getAttribute('DTEND');
        if (!is_a($end, 'PEAR_Error')) {
            $html .= sprintf('<tr><td colspan="2" class="item"><b>%s:</b>&nbsp;%s</td></tr>', _("End"), strftime("%x %X", $end));
        }

        $sum = $vevent->getAttribute('SUMMARY');
        if (!is_a($sum, 'PEAR_Error')) {
            $html .= sprintf('<tr><td colspan="2" class="item"><b>%s:</b>&nbsp;%s</td></tr>', _("Summary"), $sum);
        } else {
            $html .= sprintf('<tr><td colspan="2" class="item"><b>%s:</b>&nbsp;<i>%s</i></td></tr>', _("Summary"), _("None"));
        }

        $desc = $vevent->getAttribute('DESCRIPTION');
        if (!is_a($desc, 'PEAR_Error')) {
            $html .= sprintf('<tr><td colspan="2" class="item"><b>%s:</b><br />%s</td></tr>', _("Description"), preg_replace('/\\n/', '<br />', $desc));
        }

        $loc = $vevent->getAttribute('LOCATION');
        if (!is_a($loc, 'PEAR_Error')) {
            $html .= sprintf('<tr><td colspan="2" class="item"><b>%s:</b>&nbsp;%s</td></tr>', _("Location"), $loc);
        }

        $attendees = $vevent->getAttribute('ATTENDEE');
        $params    = $vevent->getAttribute('ATTENDEE', true);

        if (!is_a($attendees, 'PEAR_Error')) {
            $html .= sprintf('<tr><td colspan="2" class="item"><b>%s:</b><br/>', _("Attendees"));
            if (!is_array($attendees)) {
                $attendees = array($attendees);
            }

            $html .= sprintf('<table width="100%%"><tr><th align="left">%s</th><th align="left">%s</th><th align="left">%s</th></tr>', _("Name"), _("Role"), _("Status"));
            foreach ($attendees as $key => $attendee) {
                $attendee = parse_url($attendee);
                $attendee = $attendee['path'];

                if (isset($params[$key]['CN'])) {
                    $attendee = $params[$key]['CN'];
                }

                $role = _("Required Participant");
                if (isset($params[$key]['ROLE'])) {
                    switch ($params[$key]['ROLE']) {
                    case 'CHAIR':
                        $role = _("Chair Person");
                        break;

                    case 'OPT-PARTICIPANT':
                        $role = _("Optional Participant");
                        break;

                    case 'NON-PARTICIPANT':
                        $role = _("Non Participant");
                        break;

                    case 'REQ-PARTICIPANT':
                    default:
                        // Already set above.
                        break;
                    }
                }

                $status = _("Awaiting Response");
                if (isset($params[$key]['PARTSTAT'])) {
                    $status = $this->_partstatToString($params[$key]['PARTSTAT'], $status);
                }

                $html .= sprintf('<tr><td>%s</td><td>%s</td><td>%s</td></tr>', htmlspecialchars($attendee), htmlspecialchars($role), htmlspecialchars($status));
            }
            $html .= '</table></td></tr>';
        }

        if (count($options)) {
            $html .= '<tr><td colspan="2" class="control"><b>' . _("Actions") . ':</b><br/>';
            $html .= _("Choose an action:") . "&nbsp;<select name='action[$id]'>";
            $html .= implode("\n", $options);
            $html .= '</select>&nbsp<input type="submit" class="button" value="' . _("OK") . '" /><br />';
            $html .= '</td></tr>';
        }

        $html .= '</table>';
        return $html;
    }

    /**
     * Translate the Participation status to string.
     *
     * @param string $value    The value of PARTSTAT.
     * @param string $default  The value to return as default.
     *
     * @return string   The translated string.
     */
    function _partstatToString($value, $default = null)
    {
        switch ($value) {
        case 'ACCEPTED':
            return _("Accepted");
            break;

        case 'DECLINED':
            return _("Declined");
            break;

        case 'TENTATIVE':
            return _("Tentatively Accepted");
            break;

        case 'DELEGATED':
            return _("Delegated");
            break;

        case 'COMPLETED':
            return _("Completed");
            break;

        case 'IN-PROCESS':
            return _("In Process");
            break;

        case 'NEEDS-ACTION':
        default:
            return is_null($default) ? _("Needs Action") : $default;
        }
    }

    /**
     * Return text/html as the content-type.
     *
     * @return string "text/html" constant
     */
    function getType()
    {
        return 'text/html; charset=' . NLS::getCharset();
    }

    /**
     * Get the headers of the message.
     *
     * @access private
     *
     * @param object MIME_Contents &$contents  A MIME_Contents/IMP_Contents
     *                                         object.
     */
    function _getHeaders(&$contents)
    {
        if (empty($this->_headers)) {
            if (!($this->_headers = $this->mime_part->getInformation('smime_headers'))) {
                require_once IMP_BASE . '/lib/MIME/Headers.php';
                $this->_headers = &new IMP_Headers($contents->getMessageIndex());
                $this->_headers->buildHeaders();
            }
        }
    }

}
