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

The ITip 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 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/) 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.

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

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  }
parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee)
Parse an event update for an attendee.
Definition: Broker.php:645
parseEventInfo(VCalendar $calendar=null)
Returns attendee information and information about instances of an event.
Definition: Broker.php:831
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
+ 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.

References $message, $summary, Sabre\VObject\Component\add(), and Sabre\VObject\DateTimeParser\parse().

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

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
+ 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.

References $exceptions, $message, $messages, and Sabre\VObject\Component\select().

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

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;
625  $messages[] = $message;
626 
627  }
628 
629  return $messages;
630 
631  }
$messages
Definition: en.php:5
catch(Exception $e) $message
$exceptions
Definition: Utf8Test.php:67
+ Here is the call graph for this function:
+ 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.

References $calendar, and $key.

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

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  }
$key
Definition: croninfo.php:18
+ 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.

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

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  }
processMessageRequest(Message $itipMessage, VCalendar $existingObject=null)
Processes incoming REQUEST messages.
Definition: Broker.php:279
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
+ 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.

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

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  }
+ 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.

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

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  }
+ 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.

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

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  }
+ 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: