Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/mime_drivers.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,16 @@
* reply status to be explicitly updated by user action. */
'auto_update_eventreply' => false,

/* How event updates (METHOD=REQUEST for an existing event) are
* handled when a user opens the message.
* - false: The calendar is never automatically updated; requires
* explicit action by the user.
* - true: The calendar is always automatically updated.
* - Array: An array of domains for which updates are always
* automatically applied. All other domains require the
* user to explicitly update the calendar. */
'auto_update_eventrequest' => true,
Comment thread
TDannhauer marked this conversation as resolved.

/* How free/busy publish data is handled when a user opens the
* message.
* - false: Free/busy data is never automatically updated; requires
Expand Down
44 changes: 40 additions & 4 deletions lib/Ajax/Imple/ItipRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,17 +183,17 @@ protected function _handle(Variables|Horde_Variables $vars)
case 'counter-accept':
if (isset($components[$key]) && $components[$key]->getType() == 'vEvent') {
$result = $this->_handlevEvent($key, $components, $mime_part);
if ($result && $registry->hasMethod('calendar/updateAttendee')) {
if ($result && $registry->hasMethod('calendar/acceptCounterProposal')) {
try {
if ($tmp = $contents->getHeader()->getHeader('from')) {
$registry->call('calendar/updateAttendee', [
$registry->call('calendar/acceptCounterProposal', [
$components[$key],
$tmp->getAddressList(true)->first()->bare_address,
true,
]);
}
} catch (Horde_Exception $e) {
$notification->push(sprintf(_('There was an error updating the event attendee state: %s'), $e->getMessage()), 'horde.warning');
Horde::log($e, Horde_Log::ERR);
$notification->push(sprintf(_('There was an error notifying attendees of the accepted proposal: %s'), $e->getMessage()), 'horde.warning');
}
}
} else {
Expand All @@ -211,6 +211,18 @@ protected function _handle(Variables|Horde_Variables $vars)
if (empty($to)) {
throw new Horde_Exception(_("Unable to determine attendee address."));
}
if ($registry->hasMethod('calendar/declineCounterProposal')) {
try {
$registry->call('calendar/declineCounterProposal', [
$components[$key],
$to,
true,
]);
} catch (Horde_Exception $e) {
Horde::log($e, Horde_Log::ERR);
$notification->push(sprintf(_('There was an error clearing the proposed new time: %s'), $e->getMessage()), 'horde.warning');
}
}
Comment thread
Copilot marked this conversation as resolved.
$this->_sendDeclineCounter($components[$key], $to, $vars->identity);
$notification->push(_('Decline counter sent.'), 'horde.success');
$result = true;
Expand All @@ -223,6 +235,22 @@ protected function _handle(Variables|Horde_Variables $vars)
}
break;

case 'decline-counter':
if (isset($components[$key]) && $components[$key]->getType() == 'vEvent'
&& $registry->hasMethod('calendar/declineCounterProposal')) {
try {
$registry->call('calendar/declineCounterProposal', [$components[$key]]);
$notification->push(_('The proposed new time was removed from your calendar.'), 'horde.success');
$result = true;
} catch (Horde_Exception $e) {
Horde::log($e, Horde_Log::ERR);
$notification->push(sprintf(_('There was an error updating the event: %s'), $e->getMessage()), 'horde.error');
}
} else {
$notification->push(_('This action is not supported.'), 'horde.warning');
}
break;

case 'import':
case 'accept-import':
// vFreebusy reply.
Expand Down Expand Up @@ -568,6 +596,14 @@ protected function _sendDeclineCounter(
}
$headers->addHeader('Subject', _('Decline Counter Proposal'));

if (class_exists('Kronolith') && method_exists('Kronolith', 'applyDeclineCounterToLocalUser')) {
try {
Kronolith::applyDeclineCounterToLocalUser($toAddress, $vevent);
} catch (Horde_Exception $e) {
Horde::log($e, Horde_Log::ERR);
}
}

$mime->send($toAddress, $headers, $injector->getInstance('IMP_Mail'));
}

Expand Down
47 changes: 46 additions & 1 deletion lib/Mime/Viewer/Itip.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
class IMP_Mime_Viewer_Itip extends Horde_Mime_Viewer_Base
{
public const AUTO_UPDATE_EVENT_REPLY = 'auto_update_eventreply';
public const AUTO_UPDATE_EVENT_REQUEST = 'auto_update_eventrequest';
public const AUTO_UPDATE_FB_PUBLISH = 'auto_update_fbpublish';
public const AUTO_UPDATE_FB_REPLY = 'auto_update_fbreply';
public const AUTO_UPDATE_TASK_REPLY = 'auto_update_taskreply';
Expand Down Expand Up @@ -333,7 +334,34 @@ protected function _vEvent($vevent, $id, $method = 'PUBLISH', $components = [])
}
}

if ($is_update && $registry->hasMethod('calendar/replace')) {
$auto_updated = false;
if ($is_update
&& $registry->hasMethod('calendar/replace')
&& $this->_autoUpdateReply(self::AUTO_UPDATE_EVENT_REQUEST, $this->_senderFromHeader())) {
try {
$uid = $vevent->getAttributeSingle('UID');
$registry->call('calendar/replace', [
$uid,
$vevent,
'text/calendar',
]);
$url = Horde::url($registry->link('calendar/show', ['uid' => $uid]));
$notification->push(
_('The event was updated in your calendar.') . ' '
. Horde::link($url, _('View event'), null, '_blank')
. Horde_Themes_Image::tag('mime/icalendar.png', ['alt' => _('View event')])
. '</a>',
'horde.success',
['content.raw']
);
$auto_updated = true;
} catch (Horde_Exception $e) {
Horde::log($e, Horde_Log::ERR);
$notification->push(sprintf(_('There was an error updating the event: %s'), $e->getMessage()), 'horde.error');
}
}

if ($is_update && !$auto_updated && $registry->hasMethod('calendar/replace')) {
$options['accept-import'] = _('Accept and update in my calendar');
$options['import'] = _('Update in my calendar');
} elseif ($registry->hasMethod('calendar/import')) {
Expand Down Expand Up @@ -404,6 +432,23 @@ protected function _vEvent($vevent, $id, $method = 'PUBLISH', $components = [])
}
break;

case 'DECLINECOUNTER':
$desc = _('%s has declined your proposed new time for "%s".');
$sender = $this->_senderFromHeader();
if ($registry->hasMethod('calendar/declineCounterProposal')
&& $this->_autoUpdateReply(self::AUTO_UPDATE_EVENT_REQUEST, $sender)) {
try {
$registry->call('calendar/declineCounterProposal', [$vevent]);
$notification->push(_('The proposed new time was removed from your calendar.'), 'horde.success');
} catch (Horde_Exception $e) {
Horde::log($e, Horde_Log::ERR);
$notification->push(sprintf(_('There was an error updating the event: %s'), $e->getMessage()), 'horde.error');
}
} elseif ($registry->hasMethod('calendar/declineCounterProposal')) {
$options['decline-counter'] = _('Remove proposed new time from my calendar');
}
break;

case 'CANCEL':
try {
$vevent->getAttributeSingle('RECURRENCE-ID');
Expand Down
26 changes: 19 additions & 7 deletions test/Imp/Unit/Ajax/Imple/ItipRequestCounterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ public function _registryRemoteHost()
public function _registryHasMethod($method)
{
return in_array($method, [
'calendar/acceptCounterProposal',
'calendar/declineCounterProposal',
'calendar/updateAttendee',
'calendar/export',
'calendar/replace',
Expand Down Expand Up @@ -287,22 +289,32 @@ public function testCounterDeclineSendsDeclineCounterMessage()
'counter.attendee@example.com',
$this->_getMailHeaders()->getValue('To')
);

$declineCalls = array_filter(
$this->_registryCalls,
function ($call) {
return $call[0] === 'calendar/declineCounterProposal';
}
);
$this->assertCount(1, $declineCalls);
$declineCall = reset($declineCalls);
$this->assertSame('counter.attendee@example.com', $declineCall[1][1]);
$this->assertTrue($declineCall[1][2]);
}

public function testCounterAcceptStoresProposalAfterAcceptingEvent()
public function testCounterAcceptPassesAttendeeEmailToAcceptCounterProposal()
{
$this->_doRequest('counter-accept', $this->_getCounterCalendar(), 'default', true);

$updateCalls = array_filter(
$acceptCalls = array_filter(
$this->_registryCalls,
function ($call) {
return $call[0] === 'calendar/updateAttendee';
return $call[0] === 'calendar/acceptCounterProposal';
}
);
$this->assertCount(1, $updateCalls);
$updateCall = reset($updateCalls);
$this->assertTrue($updateCall[1][2]);
$this->assertSame('counter.attendee@example.com', $updateCall[1][1]);
$this->assertCount(1, $acceptCalls);
$acceptCall = reset($acceptCalls);
$this->assertSame('counter.attendee@example.com', $acceptCall[1][1]);
}

public function testCounterUpdateStoresProposalForCounterMethod()
Expand Down