115 if ($itipMessage->component !==
'VEVENT') {
119 switch ($itipMessage->method) {
136 return $existingObject;
172 if (is_string($oldCalendar)) {
175 if (!isset($oldCalendar->VEVENT)) {
184 'significantChangeHash' =>
'',
189 $userHref = (array)$userHref;
201 if (!$eventInfo[
'attendees'] && !$oldEventInfo[
'attendees']) {
206 if (!$eventInfo[
'organizer'] && !$oldEventInfo[
'organizer']) {
216 if (!$eventInfo[
'organizer'] && $oldEventInfo[
'organizer']) {
217 $eventInfo[
'organizer'] = $oldEventInfo[
'organizer'];
218 $eventInfo[
'organizerName'] = $oldEventInfo[
'organizerName'];
229 $eventInfo = $oldEventInfo;
231 if (in_array($eventInfo[
'organizer'], $userHref)) {
233 $eventInfo[
'attendees'] = [];
236 $eventInfo[
'sequence']++;
239 foreach ($eventInfo[
'attendees'] as
$key => $attendee) {
240 if (in_array($attendee[
'href'], $userHref)) {
241 $eventInfo[
'attendees'][
$key][
'instances'] = [
'master' =>
242 [
'id' =>
'master',
'partstat' =>
'DECLINED']
247 $baseCalendar = $oldCalendar;
251 if (in_array($eventInfo[
'organizer'], $userHref)) {
253 } elseif ($oldCalendar) {
257 foreach ($eventInfo[
'attendees'] as $attendee) {
258 if (in_array($attendee[
'href'], $userHref)) {
281 if (!$existingObject) {
285 foreach ($itipMessage->message->getComponents() as $component) {
286 $existingObject->add(clone $component);
292 foreach ($existingObject->getComponents() as $component) {
293 $existingObject->remove($component);
295 foreach ($itipMessage->message->getComponents() as $component) {
296 $existingObject->add(clone $component);
299 return $existingObject;
317 if (!$existingObject) {
321 foreach ($existingObject->VEVENT as $vevent) {
322 $vevent->STATUS =
'CANCELLED';
323 $vevent->SEQUENCE = $itipMessage->sequence;
326 return $existingObject;
345 if (!$existingObject) {
349 $requestStatus =
'2.0';
352 foreach ($itipMessage->message->VEVENT as $vevent) {
353 $recurId = isset($vevent->{
'RECURRENCE-ID'}) ? $vevent->{
'RECURRENCE-ID'}->getValue() :
'master';
354 $attendee = $vevent->ATTENDEE;
355 $instances[$recurId] = $attendee[
'PARTSTAT']->getValue();
356 if (isset($vevent->{
'REQUEST-STATUS'})) {
357 $requestStatus = $vevent->{
'REQUEST-STATUS'}->getValue();
358 list($requestStatus) = explode(
';', $requestStatus);
364 $masterObject = null;
365 foreach ($existingObject->VEVENT as $vevent) {
366 $recurId = isset($vevent->{
'RECURRENCE-ID'}) ? $vevent->{
'RECURRENCE-ID'}->getValue() :
'master';
367 if ($recurId ===
'master') {
368 $masterObject = $vevent;
370 if (isset($instances[$recurId])) {
371 $attendeeFound =
false;
372 if (isset($vevent->ATTENDEE)) {
373 foreach ($vevent->ATTENDEE as $attendee) {
374 if ($attendee->getValue() === $itipMessage->sender) {
375 $attendeeFound =
true;
376 $attendee[
'PARTSTAT'] = $instances[$recurId];
377 $attendee[
'SCHEDULE-STATUS'] = $requestStatus;
380 unset($attendee[
'RSVP']);
385 if (!$attendeeFound) {
388 $attendee = $vevent->add(
'ATTENDEE', $itipMessage->sender, [
389 'PARTSTAT' => $instances[$recurId]
391 if ($itipMessage->senderName) $attendee[
'CN'] = $itipMessage->senderName;
393 unset($instances[$recurId]);
397 if (!$masterObject) {
403 foreach ($instances as $recurId => $partstat) {
405 $recurrenceIterator =
new EventIterator($existingObject, $itipMessage->uid);
410 $newObject = $recurrenceIterator->getEventObject();
411 $recurrenceIterator->next();
413 if (isset($newObject->{
'RECURRENCE-ID'}) && $newObject->{
'RECURRENCE-ID'}->getValue() === $recurId) {
418 }
while ($recurrenceIterator->valid() && !$found && $iterations);
421 if (!$found)
continue;
428 $attendeeFound =
false;
429 if (isset($newObject->ATTENDEE)) {
430 foreach ($newObject->ATTENDEE as $attendee) {
431 if ($attendee->getValue() === $itipMessage->sender) {
432 $attendeeFound =
true;
433 $attendee[
'PARTSTAT'] = $partstat;
438 if (!$attendeeFound) {
440 $attendee = $newObject->add(
'ATTENDEE', $itipMessage->sender, [
441 'PARTSTAT' => $partstat
443 if ($itipMessage->senderName) {
444 $attendee[
'CN'] = $itipMessage->senderName;
447 $existingObject->add($newObject);
450 return $existingObject;
472 foreach ($oldEventInfo[
'attendees'] as $attendee) {
473 $attendees[$attendee[
'href']] = [
474 'href' => $attendee[
'href'],
475 'oldInstances' => $attendee[
'instances'],
476 'newInstances' => [],
477 'name' => $attendee[
'name'],
481 foreach ($eventInfo[
'attendees'] as $attendee) {
482 if (isset($attendees[$attendee[
'href']])) {
483 $attendees[$attendee[
'href']][
'name'] = $attendee[
'name'];
484 $attendees[$attendee[
'href']][
'newInstances'] = $attendee[
'instances'];
485 $attendees[$attendee[
'href']][
'forceSend'] = $attendee[
'forceSend'];
487 $attendees[$attendee[
'href']] = [
488 'href' => $attendee[
'href'],
489 'oldInstances' => [],
490 'newInstances' => $attendee[
'instances'],
491 'name' => $attendee[
'name'],
492 'forceSend' => $attendee[
'forceSend'],
499 foreach ($attendees as $attendee) {
503 if ($attendee[
'href'] === $eventInfo[
'organizer']) {
510 $message->sequence = $eventInfo[
'sequence'];
511 $message->sender = $eventInfo[
'organizer'];
512 $message->senderName = $eventInfo[
'organizerName'];
513 $message->recipient = $attendee[
'href'];
514 $message->recipientName = $attendee[
'name'];
516 if (!$attendee[
'newInstances']) {
525 $icalMsg->METHOD =
$message->method;
526 $event = $icalMsg->add(
'VEVENT', [
530 if (isset($calendar->VEVENT->SUMMARY)) {
531 $event->add(
'SUMMARY', $calendar->VEVENT->SUMMARY->getValue());
533 $event->add(clone $calendar->VEVENT->DTSTART);
534 if (isset($calendar->VEVENT->DTEND)) {
535 $event->add(clone $calendar->VEVENT->DTEND);
536 } elseif (isset($calendar->VEVENT->DURATION)) {
537 $event->add(clone $calendar->VEVENT->DURATION);
539 $org = $event->add(
'ORGANIZER', $eventInfo[
'organizer']);
540 if ($eventInfo[
'organizerName']) $org[
'CN'] = $eventInfo[
'organizerName'];
541 $event->add(
'ATTENDEE', $attendee[
'href'], [
542 'CN' => $attendee[
'name'],
553 $icalMsg->METHOD =
$message->method;
555 foreach ($calendar->
select(
'VTIMEZONE') as $timezone) {
556 $icalMsg->add(clone $timezone);
568 $attendee[
'forceSend'] ===
'REQUEST' ||
569 array_keys($attendee[
'oldInstances']) != array_keys($attendee[
'newInstances']) ||
570 $oldEventInfo[
'significantChangeHash'] !== $eventInfo[
'significantChangeHash'];
572 foreach ($attendee[
'newInstances'] as $instanceId => $instanceInfo) {
574 $currentEvent = clone $eventInfo[
'instances'][$instanceId];
575 if ($instanceId ===
'master') {
580 foreach ($eventInfo[
'instances'] as $instanceId => $vevent) {
581 if (!isset($attendee[
'newInstances'][$instanceId])) {
589 if (isset($currentEvent->EXDATE)) {
590 $currentEvent->EXDATE->setParts(array_merge(
591 $currentEvent->EXDATE->getParts(),
601 unset($currentEvent->ORGANIZER[
'SCHEDULE-FORCE-SEND']);
602 unset($currentEvent->ORGANIZER[
'SCHEDULE-STATUS']);
604 foreach ($currentEvent->ATTENDEE as $attendee) {
605 unset($attendee[
'SCHEDULE-FORCE-SEND']);
606 unset($attendee[
'SCHEDULE-STATUS']);
610 if (!isset($attendee[
'PARTSTAT'])) {
611 $attendee[
'PARTSTAT'] =
'NEEDS-ACTION';
618 $icalMsg->add($currentEvent);
647 if ($this->scheduleAgentServerRules && $eventInfo[
'organizerScheduleAgent'] ===
'CLIENT') {
653 if ($eventInfo[
'status'] ===
'CANCELLED') {
657 $oldInstances = !empty($oldEventInfo[
'attendees'][$attendee][
'instances']) ?
658 $oldEventInfo[
'attendees'][$attendee][
'instances'] :
662 foreach ($oldInstances as $instance) {
664 $instances[$instance[
'id']] = [
665 'id' => $instance[
'id'],
666 'oldstatus' => $instance[
'partstat'],
671 foreach ($eventInfo[
'attendees'][$attendee][
'instances'] as $instance) {
673 if (isset($instances[$instance[
'id']])) {
674 $instances[$instance[
'id']][
'newstatus'] = $instance[
'partstat'];
676 $instances[$instance[
'id']] = [
677 'id' => $instance[
'id'],
679 'newstatus' => $instance[
'partstat'],
690 if (isset($instances[
'master']) && $instances[
'master'][
'newstatus'] !==
'DECLINED') {
691 foreach ($eventInfo[
'exdate'] as $exDate) {
693 if (!in_array($exDate, $oldEventInfo[
'exdate'])) {
694 if (isset($instances[$exDate])) {
695 $instances[$exDate][
'newstatus'] =
'DECLINED';
697 $instances[$exDate] = [
700 'newstatus' =>
'DECLINED',
709 foreach ($instances as $recurId => $instanceInfo) {
711 if (isset($eventInfo[
'instances'][$recurId])) {
712 $instances[$recurId][
'dtstart'] = clone $eventInfo[
'instances'][$recurId]->DTSTART;
714 $instances[$recurId][
'dtstart'] = $recurId;
723 $message->sequence = $eventInfo[
'sequence'];
725 $message->senderName = $eventInfo[
'attendees'][$attendee][
'name'];
726 $message->recipient = $eventInfo[
'organizer'];
727 $message->recipientName = $eventInfo[
'organizerName'];
730 $icalMsg->METHOD =
'REPLY';
734 foreach ($instances as $instance) {
736 if ($instance[
'oldstatus'] == $instance[
'newstatus'] && $eventInfo[
'organizerForceSend'] !==
'REPLY') {
741 $event = $icalMsg->add(
'VEVENT', [
745 $summary = isset($calendar->VEVENT->SUMMARY) ? $calendar->VEVENT->SUMMARY->getValue() :
'';
747 if (isset($eventInfo[
'instances'][$instance[
'id']])) {
748 $instanceObj = $eventInfo[
'instances'][$instance[
'id']];
749 $event->
add(clone $instanceObj->DTSTART);
750 if (isset($instanceObj->DTEND)) {
751 $event->add(clone $instanceObj->DTEND);
752 } elseif (isset($instanceObj->DURATION)) {
753 $event->add(clone $instanceObj->DURATION);
755 if (isset($instanceObj->SUMMARY)) {
756 $event->add(
'SUMMARY', $instanceObj->SUMMARY->getValue());
767 if (strlen($instance[
'id']) <= 8) {
768 $event->add(
'DTSTART', $dt, [
'VALUE' =>
'DATE']);
770 $event->add(
'DTSTART', $dt);
776 if ($instance[
'id'] !==
'master') {
779 if (strlen($instance[
'id']) <= 8) {
780 $event->add(
'RECURRENCE-ID', $dt, [
'VALUE' =>
'DATE']);
782 $event->add(
'RECURRENCE-ID', $dt);
785 $organizer = $event->add(
'ORGANIZER',
$message->recipient);
787 $organizer[
'CN'] =
$message->recipientName;
789 $attendee = $event->add(
'ATTENDEE',
$message->sender, [
790 'PARTSTAT' => $instance[
'newstatus']
793 $attendee[
'CN'] =
$message->senderName;
835 $organizerName = null;
836 $organizerForceSend = null;
840 $organizerScheduleAgent =
'SERVER';
842 $significantChangeHash =
'';
855 $uid = $vevent->UID->getValue();
857 if ($uid !== $vevent->UID->getValue()) {
858 throw new ITipException(
'If a calendar contained more than one event, they must have the same UID.');
862 if (!isset($vevent->DTSTART)) {
863 throw new ITipException(
'An event MUST have a DTSTART property.');
866 if (isset($vevent->ORGANIZER)) {
867 if (is_null($organizer)) {
868 $organizer = $vevent->ORGANIZER->getNormalizedValue();
869 $organizerName = isset($vevent->ORGANIZER[
'CN']) ? $vevent->ORGANIZER[
'CN'] : null;
871 if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) {
875 $organizerForceSend =
876 isset($vevent->ORGANIZER[
'SCHEDULE-FORCE-SEND']) ?
877 strtoupper($vevent->ORGANIZER[
'SCHEDULE-FORCE-SEND']) :
879 $organizerScheduleAgent =
880 isset($vevent->ORGANIZER[
'SCHEDULE-AGENT']) ?
881 strtoupper((
string)$vevent->ORGANIZER[
'SCHEDULE-AGENT']) :
884 if (is_null($sequence) && isset($vevent->SEQUENCE)) {
885 $sequence = $vevent->SEQUENCE->getValue();
887 if (isset($vevent->EXDATE)) {
888 foreach ($vevent->select(
'EXDATE') as $val) {
889 $exdate = array_merge($exdate, $val->getParts());
893 if (isset($vevent->RRULE)) {
894 foreach ($vevent->select(
'RRULE') as $rr) {
895 foreach ($rr->getParts() as
$key => $val) {
897 if ($key ===
'INTERVAL' && $val == 1) {
900 if (is_array($val)) {
901 $val = implode(
',', $val);
903 $rrule[] =
"$key=$val";
908 if (isset($vevent->STATUS)) {
909 $status = strtoupper($vevent->STATUS->getValue());
912 $recurId = isset($vevent->{
'RECURRENCE-ID'}) ? $vevent->{
'RECURRENCE-ID'}->getValue() :
'master';
913 if (is_null($timezone)) {
914 if ($recurId ===
'master') {
915 $timezone = $vevent->DTSTART->getDateTime()->getTimeZone();
917 $timezone = $vevent->{
'RECURRENCE-ID'}->getDateTime()->getTimeZone();
920 if (isset($vevent->ATTENDEE)) {
921 foreach ($vevent->ATTENDEE as $attendee) {
923 if ($this->scheduleAgentServerRules &&
924 isset($attendee[
'SCHEDULE-AGENT']) &&
925 strtoupper($attendee[
'SCHEDULE-AGENT']->getValue()) ===
'CLIENT' 930 isset($attendee[
'PARTSTAT']) ?
931 strtoupper($attendee[
'PARTSTAT']) :
935 isset($attendee[
'SCHEDULE-FORCE-SEND']) ?
936 strtoupper($attendee[
'SCHEDULE-FORCE-SEND']) :
940 if (isset($attendees[$attendee->getNormalizedValue()])) {
941 $attendees[$attendee->getNormalizedValue()][
'instances'][$recurId] = [
943 'partstat' => $partStat,
944 'forceSend' => $forceSend,
947 $attendees[$attendee->getNormalizedValue()] = [
948 'href' => $attendee->getNormalizedValue(),
952 'partstat' => $partStat,
955 'name' => isset($attendee[
'CN']) ? (string)$attendee[
'CN'] : null,
956 'forceSend' => $forceSend,
961 $instances[$recurId] = $vevent;
965 foreach ($this->significantChangeProperties as $prop) {
966 if (isset($vevent->$prop)) {
967 $propertyValues = $vevent->select($prop);
969 $significantChangeHash .= $prop .
':';
971 if ($prop ===
'EXDATE') {
972 $significantChangeHash .= implode(
',', $exdate) .
';';
973 } elseif ($prop ===
'RRULE') {
974 $significantChangeHash .= implode(
',', $rrule) .
';';
976 foreach ($propertyValues as $val) {
977 $significantChangeHash .= $val->getValue() .
';';
983 $significantChangeHash = md5($significantChangeHash);
989 'organizerScheduleAgent',
990 'organizerForceSend',
996 'significantChangeHash',
static parse($date, $referenceTz=null)
Parses either a Date or DateTime, or Duration value.
The ITip class is a utility class that helps with processing so-called iTip messages.
processMessageRequest(Message $itipMessage, VCalendar $existingObject=null)
Processes incoming REQUEST messages.
select($name)
Returns an array with elements that match the specified name.
processMessage(Message $itipMessage, VCalendar $existingObject=null)
This method is used to process an incoming itip message.
This class represents an iTip message.
parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee)
Parse an event update for an attendee.
SameOrganizerForAllComponentsException.
parseEventInfo(VCalendar $calendar=null)
Returns attendee information and information about instances of an event.
catch(Exception $e) $message
parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo)
This method is used in cases where an event got updated, and we potentially need to send emails to at...
This message is emitted in case of serious problems with iTip messages.
processMessageReply(Message $itipMessage, VCalendar $existingObject=null)
Processes incoming REPLY messages.
add()
Adds a new property or component, and returns the new item.
processMessageCancel(Message $itipMessage, VCalendar $existingObject=null)
Processes incoming CANCEL messages.
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
parseEvent($calendar=null, $userHref, $oldCalendar=null)
This function parses a VCALENDAR object and figure out if any messages need to be sent...
$significantChangeProperties
$scheduleAgentServerRules