From 596c10c3a0cbf5bd4fa0d52a2d41c20ea99de679 Mon Sep 17 00:00:00 2001
From: Paul Mehrer
Date: Wed, 2 Dec 2020 15:31:42 +0100
Subject: [PATCH] fix(Calendar CalDAV) create/put event refactor re move /
displaycontainer
Change-Id: Ib162226375b286c4fff5dabdbfd585e462f5c1d5
Reviewed-on: http://gerrit.tine20.com/customers/18671
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Paul Mehrer
---
.../Calendar/Frontend/WebDAV/EventTest.php | 7 +
tine20/Calendar/Controller/Event.php | 21 +--
tine20/Calendar/Frontend/WebDAV/Event.php | 130 +++++++++++-------
tine20/Calendar/Model/Attender.php | 3 +-
4 files changed, 103 insertions(+), 58 deletions(-)
diff --git a/tests/tine20/Calendar/Frontend/WebDAV/EventTest.php b/tests/tine20/Calendar/Frontend/WebDAV/EventTest.php
index 3827e973cc7..b771d881ec2 100644
--- a/tests/tine20/Calendar/Frontend/WebDAV/EventTest.php
+++ b/tests/tine20/Calendar/Frontend/WebDAV/EventTest.php
@@ -18,6 +18,8 @@ class Calendar_Frontend_WebDAV_EventTest extends Calendar_TestCase
*/
protected $objects = array();
+ protected $_ifMatchHeader;
+
/**
* Sets up the fixture.
* This method is called before a test is executed.
@@ -53,6 +55,9 @@ public function setUp()
$_SERVER['HTTP_USER_AGENT'] = 'CalendarStore/5.0 (1127); iCal/5.0 (1535); Mac OS X/10.7.1 (11B26)';
$_SERVER['REQUEST_URI'] = 'lars';
+
+ $this->_ifMatchHeader = new Zend\Http\Header\IfMatch('shalala');
+ Tinebase_Core::getRequest()->getHeaders()->addHeader($this->_ifMatchHeader);
}
public function tearDown()
@@ -60,6 +65,7 @@ public function tearDown()
parent::tearDown();
Tinebase_Core::getPreference('Calendar')->resetAppPrefsCache();
Tinebase_Core::set(Tinebase_Core::PREFERENCES, null);
+ Tinebase_Core::getRequest()->getHeaders()->removeHeader($this->_ifMatchHeader);
}
/**
@@ -1012,6 +1018,7 @@ public function testMoveOriginEvent()
// move event (origin container)
Calendar_Frontend_WebDAV_Event::create($this->objects['initialContainer'], "$id.ics", stream_get_contents($event->get()));
$oldEvent = new Calendar_Frontend_WebDAV_Event($this->objects['sharedContainer'], "$id.ics");
+
$oldEvent->delete();
$loadedEvent = new Calendar_Frontend_WebDAV_Event($this->objects['initialContainer'], "$id.ics");
diff --git a/tine20/Calendar/Controller/Event.php b/tine20/Calendar/Controller/Event.php
index 8027ef53f2a..4500b2b3fae 100644
--- a/tine20/Calendar/Controller/Event.php
+++ b/tine20/Calendar/Controller/Event.php
@@ -2762,15 +2762,15 @@ protected function _createAttender(Calendar_Model_Attender $attender, Calendar_M
// attach to display calendar if attender has/is a useraccount
if ($userAccountId) {
- if ($calendar->type == Tinebase_Model_Container::TYPE_PERSONAL && Tinebase_Container::getInstance()->hasGrant($userAccountId, $calendar, Tinebase_Model_Grants::GRANT_ADMIN)) {
+ if ($calendar->type === Tinebase_Model_Container::TYPE_PERSONAL && Tinebase_Container::getInstance()->hasGrant($userAccountId, $calendar, Tinebase_Model_Grants::GRANT_ADMIN)) {
// if attender has admin grant to (is owner of) personal physical container, this phys. cal also gets displ. cal
$attender->displaycontainer_id = $calendar->getId();
- } else if ($attender->displaycontainer_id && $userAccountId == Tinebase_Core::getUser()->getId() && Tinebase_Container::getInstance()->hasGrant($userAccountId, $attender->displaycontainer_id, Tinebase_Model_Grants::GRANT_ADMIN)) {
- // allow user to set his own displ. cal
- $attender->displaycontainer_id = $attender->displaycontainer_id;
- } else {
- $displayCalId = self::getDefaultDisplayContainerId($userAccountId);
- $attender->displaycontainer_id = $displayCalId;
+
+ // allow user to set his own *personal* displ. cal
+ // otherwise set default display container
+ } elseif (!$attender->displaycontainer_id || $userAccountId !== Tinebase_Core::getUser()->getId() || !Tinebase_Container::getInstance()->hasGrant($userAccountId, $attender->displaycontainer_id, Tinebase_Model_Grants::GRANT_ADMIN) ||
+ Tinebase_Container::getInstance()->get($attender->displaycontainer_id)->type !== Tinebase_Model_Container::TYPE_PERSONAL) {
+ $attender->displaycontainer_id = self::getDefaultDisplayContainerId($userAccountId);
}
} else if ($attender->user_type === Calendar_Model_Attender::USERTYPE_RESOURCE) {
@@ -2884,11 +2884,12 @@ protected function _updateAttender($attender, $currentAttender, $event, $isResch
// update display calendar if attender has/is a useraccount
if ($userAccountId) {
- if ($calendar->type == Tinebase_Model_Container::TYPE_PERSONAL && Tinebase_Container::getInstance()->hasGrant($userAccountId, $calendar, Tinebase_Model_Grants::GRANT_ADMIN)) {
+ if ($calendar->type === Tinebase_Model_Container::TYPE_PERSONAL && Tinebase_Container::getInstance()->hasGrant($userAccountId, $calendar, Tinebase_Model_Grants::GRANT_ADMIN)) {
// if attender has admin grant to personal physical container, this phys. cal also gets displ. cal
$attender->displaycontainer_id = $calendar->getId();
- } else if ($userAccountId == Tinebase_Core::getUser()->getId() && Tinebase_Container::getInstance()->hasGrant($userAccountId, $attender->displaycontainer_id, Tinebase_Model_Grants::GRANT_ADMIN)) {
- // allow user to set his own displ. cal
+ } else if ($userAccountId === Tinebase_Core::getUser()->getId() && Tinebase_Container::getInstance()->hasGrant($userAccountId, $attender->displaycontainer_id, Tinebase_Model_Grants::GRANT_ADMIN) &&
+ Tinebase_Container::getInstance()->get($attender->displaycontainer_id)->type === Tinebase_Model_Container::TYPE_PERSONAL) {
+ // allow user to set his own *personal* displ. cal
$attender->displaycontainer_id = $attender->displaycontainer_id;
} else {
$attender->displaycontainer_id = $currentAttender->displaycontainer_id;
diff --git a/tine20/Calendar/Frontend/WebDAV/Event.php b/tine20/Calendar/Frontend/WebDAV/Event.php
index b8f51f9c2ce..920e7ea5496 100644
--- a/tine20/Calendar/Frontend/WebDAV/Event.php
+++ b/tine20/Calendar/Frontend/WebDAV/Event.php
@@ -234,27 +234,12 @@ public static function create(Tinebase_Model_Container $container, $name, $vobje
Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' update existing event');
$vevent = new self($container, $existingEvent);
- /** @var Calendar_Model_Event $existingEvent */
- $existingEvent = clone $existingEvent;
- $existingEvent->alarms = $event->alarms;
- $existingEvent->transp = $event->transp;
- if (null === ($contactId = $container->getOwner())) {
- $contactId = Tinebase_Core::getUser()->contact_id;
- } else {
- $contactId = Tinebase_User::getInstance()->getUserById($contactId)->contact_id;
- }
- if (null !== ($attender = $event->attendee->find('user_id', $contactId))) {
- if (null !== ($oldAttender = $existingEvent->attendee->find('user_id', $contactId))) {
- $existingEvent->attendee->removeRecord($oldAttender);
- $attender->setId($oldAttender->getId());
- }
- $existingEvent->attendee->addRecord($attender);
- }
+ $event = static::_allowOnlyAttendeeProperties($existingEvent, $event, $container);
$calCtrl = Calendar_Controller_Event::getInstance();
$oldCalenderAcl = $calCtrl->doContainerACLChecks();
try {
- if ($existingEvent->hasExternalOrganizer()) {
+ if ($event->hasExternalOrganizer()) {
$calCtrl->doContainerACLChecks(false);
}
$vobject = Calendar_Convert_Event_VCalendar_Abstract::getVObject($vobjectData);
@@ -268,10 +253,14 @@ public static function create(Tinebase_Model_Container $container, $name, $vobje
break;
}
}
- $vcalendar = $converter->fromTine20Model($existingEvent);
+ $vcalendar = $converter->fromTine20Model($event);
if (null !== $xTine20Container) {
static::_addXPropsToVEvent($vcalendar, $xTine20Container);
}
+ // set a dummy if-match header
+ if (!Tinebase_Core::getRequest()->getHeaders()->has('If-Match')) {
+ Tinebase_Core::getRequest()->getHeaders()->addHeader(new Zend\Http\Header\IfMatch('dummy'));
+ }
$vevent->put($vcalendar->serialize());
} finally {
$calCtrl->doContainerACLChecks($oldCalenderAcl);
@@ -281,6 +270,28 @@ public static function create(Tinebase_Model_Container $container, $name, $vobje
return $vevent;
}
+ protected static function _allowOnlyAttendeeProperties(Calendar_Model_Event $_origEvent, Calendar_Model_Event $_event, Tinebase_Model_Container $_container)
+ {
+ $event = clone $_origEvent;
+ $event->alarms = $_event->alarms;
+ $event->transp = $_event->transp;
+ if (null === ($contactId = $_container->getOwner())) {
+ $contactId = Tinebase_Core::getUser()->contact_id;
+ } else {
+ $contactId = Tinebase_User::getInstance()->getUserById($contactId)->contact_id;
+ }
+ /** @var Calendar_Model_Attender $attender */
+ if (null !== ($attender = $_event->attendee->find('user_id', $contactId))) {
+ if (null !== ($oldAttender = $event->attendee->find('user_id', $contactId))) {
+ $event->attendee->removeRecord($oldAttender);
+ $attender->setId($oldAttender->getId());
+ }
+ $event->attendee->addRecord($attender);
+ }
+
+ return $event;
+ }
+
/**
* @param Calendar_Convert_Event_VCalendar_Abstract $converter
* @throws \Sabre\DAV\Exception\Forbidden
@@ -508,52 +519,77 @@ public function put($cardData, $retry = true)
}
// Converting to UTF-8, if needed
$cardData = Sabre\DAV\StringUtil::ensureUTF8($cardData);
-
- #Sabre_CalDAV_ICalendarUtil::validateICalendarObject($cardData, array('VEVENT', 'VFREEBUSY'));
-
$vobject = Calendar_Convert_Event_VCalendar_Abstract::getVObject($cardData);
+ $xTine20Container = null;
foreach ($vobject->children() as $component) {
if (isset($component->{'X-TINE20-CONTAINER'})) {
- $xTine20Container = $component->{'X-TINE20-CONTAINER'};
+ $xTine20Container = $component->{'X-TINE20-CONTAINER'}->getValue();
break;
}
}
-
- // concurrency management is based on etag in CalDAV
- $event = $this->_getConverter()->toTine20Model($vobject, $this->getRecord(), array(
+
+ // clone, otherwise $this->_event and $event would be the same object
+ $event = $this->_getConverter()->toTine20Model($vobject, clone $this->getRecord(), array(
Calendar_Convert_Event_VCalendar_Abstract::OPTION_USE_SERVER_MODLOG => true,
));
if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " " . print_r($event->toArray(), true));
-
$currentEvent = $this->getRecord();
+ if (null !== $xTine20Container && $currentEvent->container_id !== $xTine20Container &&
+ $currentEvent->attendee->find('displaycontainer_id', $xTine20Container) === null) {
+ $xTine20Container = null;
+ }
+ $inDisplayContainer = $currentEvent->attendee->find('displaycontainer_id', $this->_container->getId()) !== null;
+ $currentOwnAttendee = Calendar_Model_Attender::getOwnAttender($currentEvent->attendee);
+ $xTine20ContainerAttendee = $xTine20Container ?
+ $currentEvent->attendee->find('displaycontainer_id', $xTine20Container) : null;
$currentContainer = Tinebase_Container::getInstance()->getContainerById($currentEvent->container_id);
-
- // client sends CalDAV event -> handle a container move
- if (isset($xTine20Container)) {
- if ($xTine20Container->getValue() == $currentContainer->getId()) {
+ $currentContainer->resolveGrantsAndPath();
+
+ // no If-Match header -> no moves / displaycontainer changes
+ if (!Tinebase_Core::getRequest()->getHeaders()->has('If-Match')) {
+ $event = static::_allowOnlyAttendeeProperties($currentEvent, $event, $this->_container);
+
+ // no xTineContainer
+ } elseif (null === $xTine20Container) {
+ // target container is not a current displaycontainer and we have admin on origin container or are organizer
+ // move to target container
+ if (!$inDisplayContainer && ($currentContainer->account_grants->{Tinebase_Model_Grants::GRANT_ADMIN} ||
+ $currentEvent->organizer === Tinebase_Core::getUser()->contact_id ||
+ $currentEvent->organizer === Calendar_Controller_MSEventFacade::getInstance()->getCalendarUser()->user_id)) {
+ $event->container_id = $this->_container->getId();
+ // target container is not a current displaycontainer and is not the origin container ( and we are not organizer or admin on origin)
+ // set as new displaycontainer for OUR attendee (not currentCalendarUsers attendee!)
+ } elseif (!$inDisplayContainer && $currentOwnAttendee && $event->container_id !== $this->_container->getId()) {
+ Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event, $this->_container->getId(),
+ $currentOwnAttendee);
+ } // else do nothing
+
+ } else { // we have a xTineContainer
+ // the xTineContainer is the origin container => do the move
+ if ($event->container_id === $xTine20Container) {
+ // grant check?
+ /* ($currentContainer->account_grants->{Tinebase_Model_Grants::GRANT_ADMIN} ||
+ $currentEvent->organizer === Calendar_Controller_MSEventFacade::getInstance()->getCalendarUser()->user_id)) {
+*/
$event->container_id = $this->_container->getId();
- } else {
- // @TODO allow organizer to move original cal when he edits the displaycal event?
- if ($this->_container->type == Tinebase_Model_Container::TYPE_PERSONAL) {
- Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event, $this->_container->getId());
- }
- }
- }
- // event was created by current user -> allow container move
- else if ($currentEvent->created_by == Tinebase_Core::getUser()->getId()) {
- $event->container_id = $this->_container->getId();
- }
+ // WE (not the currentCalendarUser) are an attendee and xTineContainer was our displaycontainer
+ // => change our displaycontainer
+ } elseif ($currentOwnAttendee && $currentOwnAttendee->displaycontainer_id === $xTine20Container) {
+ if ($xTine20Container !== $this->_container->getId()) {
+ Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event,
+ $this->_container->getId(), $currentOwnAttendee);
+ }
- // client sends event from iMIP invitation -> only allow displaycontainer move
- else {
- if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
- Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " X-TINE20-CONTAINER not present -> restrict container moves");
- if ($this->_container->type == Tinebase_Model_Container::TYPE_PERSONAL) {
- Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event, $this->_container->getId());
+ // problem here is:
+ // secretary has admin on boss calendar, boss forwards imip to secretary with secretary being an attendee too
+ // now what?!
+ } elseif ($xTine20ContainerAttendee && $xTine20Container !== $this->_container->getId()) {
+ Calendar_Controller_MSEventFacade::getInstance()->setDisplaycontainer($event,
+ $this->_container->getId(), $xTine20ContainerAttendee);
}
}
diff --git a/tine20/Calendar/Model/Attender.php b/tine20/Calendar/Model/Attender.php
index 3ed523c25e9..7f0c73ed3c3 100644
--- a/tine20/Calendar/Model/Attender.php
+++ b/tine20/Calendar/Model/Attender.php
@@ -3,7 +3,7 @@
* @package Calendar
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Cornelius Weiss
- * @copyright Copyright (c) 2009-2018 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright Copyright (c) 2009-2020 Metaways Infosystems GmbH (http://www.metaways.de)
*/
/**
@@ -17,6 +17,7 @@
* @property string status
* @property string status_authkey
* @property string user_type
+ * @property string displaycontainer_id
*/
class Calendar_Model_Attender extends Tinebase_Record_Abstract
{