ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Sabre\VObject\ITip\Broker Class Reference

The ITip\Broker class is a utility class that helps with processing so-called iTip messages. More...

+ Collaboration diagram for Sabre\VObject\ITip\Broker:

Public Member Functions

 processMessage (Message $itipMessage, VCalendar $existingObject=null)
 This method is used to process an incoming itip message. More...
 
 parseEvent ($calendar=null, $userHref, $oldCalendar=null)
 This function parses a VCALENDAR object and figure out if any messages need to be sent. More...
 

Data Fields

 $scheduleAgentServerRules = true
 
 $significantChangeProperties
 

Protected Member Functions

 processMessageRequest (Message $itipMessage, VCalendar $existingObject=null)
 Processes incoming REQUEST messages. More...
 
 processMessageCancel (Message $itipMessage, VCalendar $existingObject=null)
 Processes incoming CANCEL messages. More...
 
 processMessageReply (Message $itipMessage, VCalendar $existingObject=null)
 Processes incoming REPLY messages. More...
 
 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 attendees to let them know of updates in the events. More...
 
 parseEventForAttendee (VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee)
 Parse an event update for an attendee. More...
 
 parseEventInfo (VCalendar $calendar=null)
 Returns attendee information and information about instances of an event. More...
 

Detailed Description

The ITip\Broker class is a utility class that helps with processing so-called iTip messages.

iTip is defined in rfc5546, stands for iCalendar Transport-Independent Interoperability Protocol, and describes the underlying mechanism for using iCalendar for scheduling for for example through email (also known as IMip) and CalDAV Scheduling.

This class helps by:

  1. Creating individual invites based on an iCalendar event for each attendee.
  2. Generating invite updates based on an iCalendar update. This may result in new invites, updates and cancellations for attendees, if that list changed.
  3. On the receiving end, it can create a local iCalendar event based on a received invite.
  4. It can also process an invite update on a local event, ensuring that any overridden properties from attendees are retained.
  5. It can create a accepted or declined iTip reply based on an invite.
  6. It can process a reply from an invite and update an events attendee status based on a reply.
Author
Evert Pot (http://evertpot.com/) @license http://sabre.io/license/ Modified BSD License

Definition at line 38 of file Broker.php.

Member Function Documentation

◆ parseEvent()

Sabre\VObject\ITip\Broker::parseEvent (   $calendar = null,
  $userHref,
  $oldCalendar = null 
)

This function parses a VCALENDAR object and figure out if any messages need to be sent.

A VCALENDAR object will be created from the perspective of either an attendee, or an organizer. You must pass a string identifying the current user, so we can figure out who in the list of attendees or the organizer we are sending this message on behalf of.

It's possible to specify the current user as an array, in case the user has more than one identifying href (such as multiple emails).

It $oldCalendar is specified, it is assumed that the operation is updating an existing event, which means that we need to look at the differences between events, and potentially send old attendees cancellations, and current attendees updates.

If $calendar is null, but $oldCalendar is specified, we treat the operation as if the user has deleted an event. If the user was an organizer, this means that we need to send cancellation notices to people. If the user was an attendee, we need to make sure that the organizer gets the 'declined' message.

Parameters
VCalendar | string$calendar
string | array$userHref
VCalendar | string$oldCalendar
Returns
array

Definition at line 169 of file Broker.php.

169 {
170
171 if ($oldCalendar) {
172 if (is_string($oldCalendar)) {
173 $oldCalendar = Reader::read($oldCalendar);
174 }
175 if (!isset($oldCalendar->VEVENT)) {
176 // We only support events at the moment
177 return [];
178 }
179
180 $oldEventInfo = $this->parseEventInfo($oldCalendar);
181 } else {
182 $oldEventInfo = [
183 'organizer' => null,
184 'significantChangeHash' => '',
185 'attendees' => [],
186 ];
187 }
188
189 $userHref = (array)$userHref;
190
191 if (!is_null($calendar)) {
192
193 if (is_string($calendar)) {
195 }
196 if (!isset($calendar->VEVENT)) {
197 // We only support events at the moment
198 return [];
199 }
200 $eventInfo = $this->parseEventInfo($calendar);
201 if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) {
202 // If there were no attendees on either side of the equation,
203 // we don't need to do anything.
204 return [];
205 }
206 if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) {
207 // There was no organizer before or after the change.
208 return [];
209 }
210
211 $baseCalendar = $calendar;
212
213 // If the new object didn't have an organizer, the organizer
214 // changed the object from a scheduling object to a non-scheduling
215 // object. We just copy the info from the old object.
216 if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) {
217 $eventInfo['organizer'] = $oldEventInfo['organizer'];
218 $eventInfo['organizerName'] = $oldEventInfo['organizerName'];
219 }
220
221 } else {
222 // The calendar object got deleted, we need to process this as a
223 // cancellation / decline.
224 if (!$oldCalendar) {
225 // No old and no new calendar, there's no thing to do.
226 return [];
227 }
228
229 $eventInfo = $oldEventInfo;
230
231 if (in_array($eventInfo['organizer'], $userHref)) {
232 // This is an organizer deleting the event.
233 $eventInfo['attendees'] = [];
234 // Increasing the sequence, but only if the organizer deleted
235 // the event.
236 $eventInfo['sequence']++;
237 } else {
238 // This is an attendee deleting the event.
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']
243 ];
244 }
245 }
246 }
247 $baseCalendar = $oldCalendar;
248
249 }
250
251 if (in_array($eventInfo['organizer'], $userHref)) {
252 return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
253 } elseif ($oldCalendar) {
254 // We need to figure out if the user is an attendee, but we're only
255 // doing so if there's an oldCalendar, because we only want to
256 // process updates, not creation of new events.
257 foreach ($eventInfo['attendees'] as $attendee) {
258 if (in_array($attendee['href'], $userHref)) {
259 return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
260 }
261 }
262 }
263 return [];
264
265 }
parseEventInfo(VCalendar $calendar=null)
Returns attendee information and information about instances of an event.
Definition: Broker.php:831
parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee)
Parse an event update for an attendee.
Definition: Broker.php:645
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...
Definition: Broker.php:468
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42
$key
Definition: croninfo.php:18

References $calendar, $key, Sabre\VObject\ITip\Broker\parseEventForAttendee(), Sabre\VObject\ITip\Broker\parseEventForOrganizer(), Sabre\VObject\ITip\Broker\parseEventInfo(), and Sabre\VObject\Reader\read().

+ Here is the call graph for this function:

◆ parseEventForAttendee()

Sabre\VObject\ITip\Broker::parseEventForAttendee ( VCalendar  $calendar,
array  $eventInfo,
array  $oldEventInfo,
  $attendee 
)
protected

Parse an event update for an attendee.

This function figures out if we need to send a reply to an organizer.

Parameters
VCalendar$calendar
array$eventInfo
array$oldEventInfo
string$attendee
Returns
Message[]

Definition at line 645 of file Broker.php.

645 {
646
647 if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent'] === 'CLIENT') {
648 return [];
649 }
650
651 // Don't bother generating messages for events that have already been
652 // cancelled.
653 if ($eventInfo['status'] === 'CANCELLED') {
654 return [];
655 }
656
657 $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ?
658 $oldEventInfo['attendees'][$attendee]['instances'] :
659 [];
660
661 $instances = [];
662 foreach ($oldInstances as $instance) {
663
664 $instances[$instance['id']] = [
665 'id' => $instance['id'],
666 'oldstatus' => $instance['partstat'],
667 'newstatus' => null,
668 ];
669
670 }
671 foreach ($eventInfo['attendees'][$attendee]['instances'] as $instance) {
672
673 if (isset($instances[$instance['id']])) {
674 $instances[$instance['id']]['newstatus'] = $instance['partstat'];
675 } else {
676 $instances[$instance['id']] = [
677 'id' => $instance['id'],
678 'oldstatus' => null,
679 'newstatus' => $instance['partstat'],
680 ];
681 }
682
683 }
684
685 // We need to also look for differences in EXDATE. If there are new
686 // items in EXDATE, it means that an attendee deleted instances of an
687 // event, which means we need to send DECLINED specifically for those
688 // instances.
689 // We only need to do that though, if the master event is not declined.
690 if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') {
691 foreach ($eventInfo['exdate'] as $exDate) {
692
693 if (!in_array($exDate, $oldEventInfo['exdate'])) {
694 if (isset($instances[$exDate])) {
695 $instances[$exDate]['newstatus'] = 'DECLINED';
696 } else {
697 $instances[$exDate] = [
698 'id' => $exDate,
699 'oldstatus' => null,
700 'newstatus' => 'DECLINED',
701 ];
702 }
703 }
704
705 }
706 }
707
708 // Gathering a few extra properties for each instance.
709 foreach ($instances as $recurId => $instanceInfo) {
710
711 if (isset($eventInfo['instances'][$recurId])) {
712 $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART;
713 } else {
714 $instances[$recurId]['dtstart'] = $recurId;
715 }
716
717 }
718
719 $message = new Message();
720 $message->uid = $eventInfo['uid'];
721 $message->method = 'REPLY';
722 $message->component = 'VEVENT';
723 $message->sequence = $eventInfo['sequence'];
724 $message->sender = $attendee;
725 $message->senderName = $eventInfo['attendees'][$attendee]['name'];
726 $message->recipient = $eventInfo['organizer'];
727 $message->recipientName = $eventInfo['organizerName'];
728
729 $icalMsg = new VCalendar();
730 $icalMsg->METHOD = 'REPLY';
731
732 $hasReply = false;
733
734 foreach ($instances as $instance) {
735
736 if ($instance['oldstatus'] == $instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') {
737 // Skip
738 continue;
739 }
740
741 $event = $icalMsg->add('VEVENT', [
742 'UID' => $message->uid,
743 'SEQUENCE' => $message->sequence,
744 ]);
745 $summary = isset($calendar->VEVENT->SUMMARY) ? $calendar->VEVENT->SUMMARY->getValue() : '';
746 // Adding properties from the correct source instance
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);
754 }
755 if (isset($instanceObj->SUMMARY)) {
756 $event->add('SUMMARY', $instanceObj->SUMMARY->getValue());
757 } elseif ($summary) {
758 $event->add('SUMMARY', $summary);
759 }
760 } else {
761 // This branch of the code is reached, when a reply is
762 // generated for an instance of a recurring event, through the
763 // fact that the instance has disappeared by showing up in
764 // EXDATE
765 $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
766 // Treat is as a DATE field
767 if (strlen($instance['id']) <= 8) {
768 $event->add('DTSTART', $dt, ['VALUE' => 'DATE']);
769 } else {
770 $event->add('DTSTART', $dt);
771 }
772 if ($summary) {
773 $event->add('SUMMARY', $summary);
774 }
775 }
776 if ($instance['id'] !== 'master') {
777 $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
778 // Treat is as a DATE field
779 if (strlen($instance['id']) <= 8) {
780 $event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']);
781 } else {
782 $event->add('RECURRENCE-ID', $dt);
783 }
784 }
785 $organizer = $event->add('ORGANIZER', $message->recipient);
786 if ($message->recipientName) {
787 $organizer['CN'] = $message->recipientName;
788 }
789 $attendee = $event->add('ATTENDEE', $message->sender, [
790 'PARTSTAT' => $instance['newstatus']
791 ]);
792 if ($message->senderName) {
793 $attendee['CN'] = $message->senderName;
794 }
795 $hasReply = true;
796
797 }
798
799 if ($hasReply) {
800 $message->message = $icalMsg;
801 return [$message];
802 } else {
803 return [];
804 }
805
806 }
static parse($date, $referenceTz=null)
Parses either a Date or DateTime, or Duration value.
$summary
Definition: cron.php:24
catch(Exception $e) $message

References $calendar, $message, $summary, and Sabre\Uri\parse().

Referenced by Sabre\VObject\ITip\Broker\parseEvent().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ parseEventForOrganizer()

Sabre\VObject\ITip\Broker::parseEventForOrganizer ( VCalendar  $calendar,
array  $eventInfo,
array  $oldEventInfo 
)
protected

This method is used in cases where an event got updated, and we potentially need to send emails to attendees to let them know of updates in the events.

We will detect which attendees got added, which got removed and create specific messages for these situations.

Parameters
VCalendar$calendar
array$eventInfo
array$oldEventInfo
Returns
array

Definition at line 468 of file Broker.php.

468 {
469
470 // Merging attendee lists.
471 $attendees = [];
472 foreach ($oldEventInfo['attendees'] as $attendee) {
473 $attendees[$attendee['href']] = [
474 'href' => $attendee['href'],
475 'oldInstances' => $attendee['instances'],
476 'newInstances' => [],
477 'name' => $attendee['name'],
478 'forceSend' => null,
479 ];
480 }
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'];
486 } else {
487 $attendees[$attendee['href']] = [
488 'href' => $attendee['href'],
489 'oldInstances' => [],
490 'newInstances' => $attendee['instances'],
491 'name' => $attendee['name'],
492 'forceSend' => $attendee['forceSend'],
493 ];
494 }
495 }
496
497 $messages = [];
498
499 foreach ($attendees as $attendee) {
500
501 // An organizer can also be an attendee. We should not generate any
502 // messages for those.
503 if ($attendee['href'] === $eventInfo['organizer']) {
504 continue;
505 }
506
507 $message = new Message();
508 $message->uid = $eventInfo['uid'];
509 $message->component = 'VEVENT';
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'];
515
516 if (!$attendee['newInstances']) {
517
518 // If there are no instances the attendee is a part of, it
519 // means the attendee was removed and we need to send him a
520 // CANCEL.
521 $message->method = 'CANCEL';
522
523 // Creating the new iCalendar body.
524 $icalMsg = new VCalendar();
525 $icalMsg->METHOD = $message->method;
526 $event = $icalMsg->add('VEVENT', [
527 'UID' => $message->uid,
528 'SEQUENCE' => $message->sequence,
529 ]);
530 if (isset($calendar->VEVENT->SUMMARY)) {
531 $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue());
532 }
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);
538 }
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'],
543 ]);
544 $message->significantChange = true;
545
546 } else {
547
548 // The attendee gets the updated event body
549 $message->method = 'REQUEST';
550
551 // Creating the new iCalendar body.
552 $icalMsg = new VCalendar();
553 $icalMsg->METHOD = $message->method;
554
555 foreach ($calendar->select('VTIMEZONE') as $timezone) {
556 $icalMsg->add(clone $timezone);
557 }
558
559 // We need to find out that this change is significant. If it's
560 // not, systems may opt to not send messages.
561 //
562 // We do this based on the 'significantChangeHash' which is
563 // some value that changes if there's a certain set of
564 // properties changed in the event, or simply if there's a
565 // difference in instances that the attendee is invited to.
566
567 $message->significantChange =
568 $attendee['forceSend'] === 'REQUEST' ||
569 array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) ||
570 $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];
571
572 foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) {
573
574 $currentEvent = clone $eventInfo['instances'][$instanceId];
575 if ($instanceId === 'master') {
576
577 // We need to find a list of events that the attendee
578 // is not a part of to add to the list of exceptions.
579 $exceptions = [];
580 foreach ($eventInfo['instances'] as $instanceId => $vevent) {
581 if (!isset($attendee['newInstances'][$instanceId])) {
582 $exceptions[] = $instanceId;
583 }
584 }
585
586 // If there were exceptions, we need to add it to an
587 // existing EXDATE property, if it exists.
588 if ($exceptions) {
589 if (isset($currentEvent->EXDATE)) {
590 $currentEvent->EXDATE->setParts(array_merge(
591 $currentEvent->EXDATE->getParts(),
593 ));
594 } else {
595 $currentEvent->EXDATE = $exceptions;
596 }
597 }
598
599 // Cleaning up any scheduling information that
600 // shouldn't be sent along.
601 unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
602 unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);
603
604 foreach ($currentEvent->ATTENDEE as $attendee) {
605 unset($attendee['SCHEDULE-FORCE-SEND']);
606 unset($attendee['SCHEDULE-STATUS']);
607
608 // We're adding PARTSTAT=NEEDS-ACTION to ensure that
609 // iOS shows an "Inbox Item"
610 if (!isset($attendee['PARTSTAT'])) {
611 $attendee['PARTSTAT'] = 'NEEDS-ACTION';
612 }
613
614 }
615
616 }
617
618 $icalMsg->add($currentEvent);
619
620 }
621
622 }
623
624 $message->message = $icalMsg;
626
627 }
628
629 return $messages;
630
631 }
$exceptions
Definition: Utf8Test.php:67
$messages
Definition: en.php:5

References $calendar, $exceptions, $message, and $messages.

Referenced by Sabre\VObject\ITip\Broker\parseEvent().

+ Here is the caller graph for this function:

◆ parseEventInfo()

Sabre\VObject\ITip\Broker::parseEventInfo ( VCalendar  $calendar = null)
protected

Returns attendee information and information about instances of an event.

Returns an array with the following keys:

  1. uid
  2. organizer
  3. organizerName
  4. organizerScheduleAgent
  5. organizerForceSend
  6. instances
  7. attendees
  8. sequence
  9. exdate
  10. timezone - strictly the timezone on which the recurrence rule is based on.
  11. significantChangeHash
  12. status
    Parameters
    VCalendar$calendar
    Returns
    array

Definition at line 831 of file Broker.php.

831 {
832
833 $uid = null;
834 $organizer = null;
835 $organizerName = null;
836 $organizerForceSend = null;
837 $sequence = null;
838 $timezone = null;
839 $status = null;
840 $organizerScheduleAgent = 'SERVER';
841
842 $significantChangeHash = '';
843
844 // Now we need to collect a list of attendees, and which instances they
845 // are a part of.
846 $attendees = [];
847
848 $instances = [];
849 $exdate = [];
850
851 foreach ($calendar->VEVENT as $vevent) {
852 $rrule = [];
853
854 if (is_null($uid)) {
855 $uid = $vevent->UID->getValue();
856 } else {
857 if ($uid !== $vevent->UID->getValue()) {
858 throw new ITipException('If a calendar contained more than one event, they must have the same UID.');
859 }
860 }
861
862 if (!isset($vevent->DTSTART)) {
863 throw new ITipException('An event MUST have a DTSTART property.');
864 }
865
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;
870 } else {
871 if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) {
872 throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
873 }
874 }
875 $organizerForceSend =
876 isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ?
877 strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) :
878 null;
879 $organizerScheduleAgent =
880 isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ?
881 strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) :
882 'SERVER';
883 }
884 if (is_null($sequence) && isset($vevent->SEQUENCE)) {
885 $sequence = $vevent->SEQUENCE->getValue();
886 }
887 if (isset($vevent->EXDATE)) {
888 foreach ($vevent->select('EXDATE') as $val) {
889 $exdate = array_merge($exdate, $val->getParts());
890 }
891 sort($exdate);
892 }
893 if (isset($vevent->RRULE)) {
894 foreach ($vevent->select('RRULE') as $rr) {
895 foreach ($rr->getParts() as $key => $val) {
896 // ignore default values (https://github.com/sabre-io/vobject/issues/126)
897 if ($key === 'INTERVAL' && $val == 1) {
898 continue;
899 }
900 if (is_array($val)) {
901 $val = implode(',', $val);
902 }
903 $rrule[] = "$key=$val";
904 }
905 }
906 sort($rrule);
907 }
908 if (isset($vevent->STATUS)) {
909 $status = strtoupper($vevent->STATUS->getValue());
910 }
911
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();
916 } else {
917 $timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
918 }
919 }
920 if (isset($vevent->ATTENDEE)) {
921 foreach ($vevent->ATTENDEE as $attendee) {
922
923 if ($this->scheduleAgentServerRules &&
924 isset($attendee['SCHEDULE-AGENT']) &&
925 strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT'
926 ) {
927 continue;
928 }
929 $partStat =
930 isset($attendee['PARTSTAT']) ?
931 strtoupper($attendee['PARTSTAT']) :
932 'NEEDS-ACTION';
933
934 $forceSend =
935 isset($attendee['SCHEDULE-FORCE-SEND']) ?
936 strtoupper($attendee['SCHEDULE-FORCE-SEND']) :
937 null;
938
939
940 if (isset($attendees[$attendee->getNormalizedValue()])) {
941 $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = [
942 'id' => $recurId,
943 'partstat' => $partStat,
944 'forceSend' => $forceSend,
945 ];
946 } else {
947 $attendees[$attendee->getNormalizedValue()] = [
948 'href' => $attendee->getNormalizedValue(),
949 'instances' => [
950 $recurId => [
951 'id' => $recurId,
952 'partstat' => $partStat,
953 ],
954 ],
955 'name' => isset($attendee['CN']) ? (string)$attendee['CN'] : null,
956 'forceSend' => $forceSend,
957 ];
958 }
959
960 }
961 $instances[$recurId] = $vevent;
962
963 }
964
965 foreach ($this->significantChangeProperties as $prop) {
966 if (isset($vevent->$prop)) {
967 $propertyValues = $vevent->select($prop);
968
969 $significantChangeHash .= $prop . ':';
970
971 if ($prop === 'EXDATE') {
972 $significantChangeHash .= implode(',', $exdate) . ';';
973 } elseif ($prop === 'RRULE') {
974 $significantChangeHash .= implode(',', $rrule) . ';';
975 } else {
976 foreach ($propertyValues as $val) {
977 $significantChangeHash .= $val->getValue() . ';';
978 }
979 }
980 }
981 }
982 }
983 $significantChangeHash = md5($significantChangeHash);
984
985 return compact(
986 'uid',
987 'organizer',
988 'organizerName',
989 'organizerScheduleAgent',
990 'organizerForceSend',
991 'instances',
992 'attendees',
993 'sequence',
994 'exdate',
995 'timezone',
996 'significantChangeHash',
997 'status'
998 );
999
1000 }

References $calendar, and $key.

Referenced by Sabre\VObject\ITip\Broker\parseEvent().

+ Here is the caller graph for this function:

◆ processMessage()

Sabre\VObject\ITip\Broker::processMessage ( Message  $itipMessage,
VCalendar  $existingObject = null 
)

This method is used to process an incoming itip message.

Examples:

  1. A user is an attendee to an event. The organizer sends an updated meeting using a new iTip message with METHOD:REQUEST. This function will process the message and update the attendee's event accordingly.
  2. The organizer cancelled the event using METHOD:CANCEL. We will update the users event to state STATUS:CANCELLED.
  3. An attendee sent a reply to an invite using METHOD:REPLY. We can update the organizers event to update the ATTENDEE with its correct PARTSTAT.

The $existingObject is updated in-place. If there is no existing object (because it's a new invite for example) a new object will be created.

If an existing object does not exist, and the method was CANCEL or REPLY, the message effectively gets ignored, and no 'existingObject' will be created.

The updated $existingObject is also returned from this function.

If the iTip message was not supported, we will always return false.

Parameters
Message$itipMessage
VCalendar$existingObject
Returns
VCalendar|null

Definition at line 112 of file Broker.php.

112 {
113
114 // We only support events at the moment.
115 if ($itipMessage->component !== 'VEVENT') {
116 return false;
117 }
118
119 switch ($itipMessage->method) {
120
121 case 'REQUEST' :
122 return $this->processMessageRequest($itipMessage, $existingObject);
123
124 case 'CANCEL' :
125 return $this->processMessageCancel($itipMessage, $existingObject);
126
127 case 'REPLY' :
128 return $this->processMessageReply($itipMessage, $existingObject);
129
130 default :
131 // Unsupported iTip message
132 return;
133
134 }
135
136 return $existingObject;
137
138 }
processMessageReply(Message $itipMessage, VCalendar $existingObject=null)
Processes incoming REPLY messages.
Definition: Broker.php:341
processMessageCancel(Message $itipMessage, VCalendar $existingObject=null)
Processes incoming CANCEL messages.
Definition: Broker.php:315
processMessageRequest(Message $itipMessage, VCalendar $existingObject=null)
Processes incoming REQUEST messages.
Definition: Broker.php:279

References Sabre\VObject\ITip\Broker\processMessageCancel(), Sabre\VObject\ITip\Broker\processMessageReply(), and Sabre\VObject\ITip\Broker\processMessageRequest().

+ Here is the call graph for this function:

◆ processMessageCancel()

Sabre\VObject\ITip\Broker::processMessageCancel ( Message  $itipMessage,
VCalendar  $existingObject = null 
)
protected

Processes incoming CANCEL messages.

This is a message from an organizer, and means that either an attendee got removed from an event, or an event got cancelled altogether.

Parameters
Message$itipMessage
VCalendar$existingObject
Returns
VCalendar|null

Definition at line 315 of file Broker.php.

315 {
316
317 if (!$existingObject) {
318 // The event didn't exist in the first place, so we're just
319 // ignoring this message.
320 } else {
321 foreach ($existingObject->VEVENT as $vevent) {
322 $vevent->STATUS = 'CANCELLED';
323 $vevent->SEQUENCE = $itipMessage->sequence;
324 }
325 }
326 return $existingObject;
327
328 }

Referenced by Sabre\VObject\ITip\Broker\processMessage().

+ Here is the caller graph for this function:

◆ processMessageReply()

Sabre\VObject\ITip\Broker::processMessageReply ( Message  $itipMessage,
VCalendar  $existingObject = null 
)
protected

Processes incoming REPLY messages.

The message is a reply. This is for example an attendee telling an organizer he accepted the invite, or declined it.

Parameters
Message$itipMessage
VCalendar$existingObject
Returns
VCalendar|null

Definition at line 341 of file Broker.php.

341 {
342
343 // A reply can only be processed based on an existing object.
344 // If the object is not available, the reply is ignored.
345 if (!$existingObject) {
346 return;
347 }
348 $instances = [];
349 $requestStatus = '2.0';
350
351 // Finding all the instances the attendee replied to.
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);
359 }
360 }
361
362 // Now we need to loop through the original organizer event, to find
363 // all the instances where we have a reply for.
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;
369 }
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;
378 // Un-setting the RSVP status, because we now know
379 // that the attendee already replied.
380 unset($attendee['RSVP']);
381 break;
382 }
383 }
384 }
385 if (!$attendeeFound) {
386 // Adding a new attendee. The iTip documentation calls this
387 // a party crasher.
388 $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, [
389 'PARTSTAT' => $instances[$recurId]
390 ]);
391 if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName;
392 }
393 unset($instances[$recurId]);
394 }
395 }
396
397 if (!$masterObject) {
398 // No master object, we can't add new instances.
399 return;
400 }
401 // If we got replies to instances that did not exist in the
402 // original list, it means that new exceptions must be created.
403 foreach ($instances as $recurId => $partstat) {
404
405 $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
406 $found = false;
407 $iterations = 1000;
408 do {
409
410 $newObject = $recurrenceIterator->getEventObject();
411 $recurrenceIterator->next();
412
413 if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
414 $found = true;
415 }
416 $iterations--;
417
418 } while ($recurrenceIterator->valid() && !$found && $iterations);
419
420 // Invalid recurrence id. Skipping this object.
421 if (!$found) continue;
422
423 unset(
424 $newObject->RRULE,
425 $newObject->EXDATE,
426 $newObject->RDATE
427 );
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;
434 break;
435 }
436 }
437 }
438 if (!$attendeeFound) {
439 // Adding a new attendee
440 $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, [
441 'PARTSTAT' => $partstat
442 ]);
443 if ($itipMessage->senderName) {
444 $attendee['CN'] = $itipMessage->senderName;
445 }
446 }
447 $existingObject->add($newObject);
448
449 }
450 return $existingObject;
451
452 }

Referenced by Sabre\VObject\ITip\Broker\processMessage().

+ Here is the caller graph for this function:

◆ processMessageRequest()

Sabre\VObject\ITip\Broker::processMessageRequest ( Message  $itipMessage,
VCalendar  $existingObject = null 
)
protected

Processes incoming REQUEST messages.

This is message from an organizer, and is either a new event invite, or an update to an existing one.

Parameters
Message$itipMessage
VCalendar$existingObject
Returns
VCalendar|null

Definition at line 279 of file Broker.php.

279 {
280
281 if (!$existingObject) {
282 // This is a new invite, and we're just going to copy over
283 // all the components from the invite.
284 $existingObject = new VCalendar();
285 foreach ($itipMessage->message->getComponents() as $component) {
286 $existingObject->add(clone $component);
287 }
288 } else {
289 // We need to update an existing object with all the new
290 // information. We can just remove all existing components
291 // and create new ones.
292 foreach ($existingObject->getComponents() as $component) {
293 $existingObject->remove($component);
294 }
295 foreach ($itipMessage->message->getComponents() as $component) {
296 $existingObject->add(clone $component);
297 }
298 }
299 return $existingObject;
300
301 }

Referenced by Sabre\VObject\ITip\Broker\processMessage().

+ Here is the caller graph for this function:

Field Documentation

◆ $scheduleAgentServerRules

Sabre\VObject\ITip\Broker::$scheduleAgentServerRules = true

Definition at line 55 of file Broker.php.

◆ $significantChangeProperties

Sabre\VObject\ITip\Broker::$significantChangeProperties
Initial value:
= [
'DTSTART',
'DTEND',
'DURATION',
'DUE',
'RRULE',
'RDATE',
'EXDATE',
'STATUS',
]

Definition at line 69 of file Broker.php.


The documentation for this class was generated from the following file: