<?php
/**
 * Copyright 2015-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.horde.org/licenses/gpl.
 *
 * @author   Michael J Rubinsky <mrubinsk@horde.org>
 * @license  http://www.horde.org/licenses/gpl GPL
 * @package  Kronolith
 * @category Horde
 */

/**
 * Wraps logic responsible for importing iCalendar data via DAV taking into
 * account necessary steps to deal with recurrence series and exceptions.
 *
 * @author   Michael J Rubinsky <mrubinsk@horde.org>
 * @license  http://www.horde.org/licenses/gpl GPL
 * @package  Kronolith
 * @category Horde
 */
class Kronolith_Icalendar_Handler_Dav extends Kronolith_Icalendar_Handler_Base
{
    /**
     * The DAV storage driver.
     *
     * @var Horde_Dav_Storage_Base
     */
    protected $_dav;

    /**
     * The calendar id to be imported into.
     *
     * @var string
     */
    protected $_calendar;

    /**
     * Temporary cache of the existing copy of an event being replaced.
     *
     * @var Kronolith_Event
     */
    protected $_existingEvent;

    /**
     * List of attendees that should not be sent iTip notifications.
     *
     * @var  array
     */
    protected $_noItips = array();

    /**
     *
     * @param Horde_Icalendar  $iCal    The iCalendar data.
     * @param Kronolith_Driver $driver  The Kronolith driver.
     * @param array            $params  Any additional parameters needed for
     *                                  the importer. For this driver we
     *                                  require: 'object' - contains the DAV
     *                                  identifier for the (base) event.
     */
    public function __construct(
        Horde_Icalendar $iCal, Kronolith_Driver $driver, $params = array())
    {
        parent::__construct($iCal, $driver, $params);
        $this->_dav = $GLOBALS['injector']->getInstance('Horde_Dav_Storage');
        $this->_calendar = $this->_driver->calendar;
    }

    /**
     * Responsible for any logic needed before the event is saved. Called for
     * EVERY component in the iCalendar object. Returning false from this method
     * will cause the current component to be ignored. Returning true causes it
     * to be processed.
     *
     * @param  Horde_Icalendar $component  The iCalendar component.
     *
     * @return boolean  True to continue processing, false to ignore.
     */
    protected function _preSave($component)
    {
        // Short circuit if we know we don't pass the parent test.
        if (!parent::_preSave($component)) {
            return false;
        }

        // Ensure we start with a fresh state.
        $this->_existingEvent = null;

        // Get the internal id of the existing copy of the event, if it exists.
        try {
            $existing_id = $this->_dav->getInternalObjectId($this->_params['object'], $this->_calendar)
                ?: preg_replace('/\.ics$/', '', $this->_params['object']);
        } catch (Horde_Dav_Exception $e) {
            $existing_id = $this->_params['object'];
        }

        // Check that we don't have newer information already on the server.
        try {
            // Exception event, so we can't compare timestamps using ids.
            // Instead look for baseid/recurrence-id.
            $rid = $component->getAttribute('RECURRENCE-ID');
            $uid = $component->getAttribute('UID');
            if (!empty($rid) && !empty($uid)) {
                $search = new stdClass();
                $search->baseid = $uid;
                $search->recurrenceid = $rid;
                $results = $this->_driver->search($search);
                foreach ($results as $days) {
                    foreach ($days as $exception) {
                        // Should only be one...
                        $modified = $exception->modified
                            ?: $exception->created;
                    }
                }
            }
        } catch (Horde_Icalendar_Exception $e) {
            // Base event or event with no recurrence.
           try {
                $this->_existingEvent = $this->_driver->getEvent($existing_id);
                $this->_existingEvent->loadHistory();
                $modified = $this->_existingEvent->modified
                    ?: $this->_existingEvent->created;
            } catch (Horde_Exception_NotFound $e) {
                $this->_existingEvent = null;
            }
        }

        try {
            if (!empty($modified) &&
                $component->getAttribute('LAST-MODIFIED') < $modified->timestamp()) {
                 /* LAST-MODIFIED timestamp of existing entry is newer:
                 * don't replace it. */
                return false;
            }
        } catch (Horde_Icalendar_Exception $e) {}

        try {
            $organizer = $component->getAttribute('ORGANIZER');
            $organizer_params = $component->getAttribute('ORGANIZER', true);
            if (!empty($organizer_params[0]['SCHEDULE-AGENT']) &&
                ($organizer_params[0]['SCHEDULE-AGENT'] == 'CLIENT' ||
                 $organizer_params[0]['SCHEDULE-AGENT'] == 'NONE')) {
                $tmp = str_replace(array('MAILTO:', 'mailto:'), '', $organizer);
                $tmp = new Horde_Mail_Rfc822_Address($tmp);
                $this->_noItips[] = $tmp->bare_address;
            }
        } catch (Horde_Icalendar_Exception $e) {}
        try {
            $attendee = $component->getAttribute('ATTENDEE');
            if (!is_array($attendee)) {
                $attendee = array($attendee);
            }
            $params = $component->getAttribute('ATTENDEE', true);
            for ($i = 0; $i < count($attendee); ++$i) {
                if (!empty($params[$i]['SCHEDULE-AGENT']) &&
                    ($params[$i]['SCHEDULE-AGENT'] == 'CLIENT' ||
                     $params[$i]['SCHEDULE-AGENT'] == 'NONE')) {
                    $tmp = str_replace(array('MAILTO:', 'mailto:'), '', $attendee[$i]);
                    $tmp = new Horde_Mail_Rfc822_Address($tmp);
                    $this->_noItips[] = $tmp->bare_address;
                }
            }
        } catch (Horde_Icalendar_Exception $e) {}

        return true;
    }

    protected function _postSave(Kronolith_Event $event)
    {
        if (!$this->_dav->getInternalObjectId($this->_params['object'], $this->_calendar)) {
            $this->_dav->addObjectMap($event->id, $this->_params['object'], $this->_calendar);
        }

        // Send iTip messages.
        // Notifications will get lost, there is no way to return messages
        // to clients.
        $event_copy = clone($event);
        $event_copy->attendees = array_diff_key($event->attendees, array_flip($this->_noItips));
        Kronolith::sendITipNotifications(
            $event_copy,
            new Horde_Notification_Handler(new Horde_Notification_Storage_Object()),
            Kronolith::ITIP_REQUEST
        );
    }

}