protected function newEditableObject()
 {
     return PhabricatorCalendarEvent::initializeNewCalendarEvent($this->getViewer());
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getViewer();
     $user_phid = $viewer->getPHID();
     $this->id = $request->getURIData('id');
     $error_name = true;
     $error_recurrence_end_date = null;
     $error_start_date = true;
     $error_end_date = true;
     $validation_exception = null;
     $is_recurring_id = celerity_generate_unique_node_id();
     $recurrence_end_date_id = celerity_generate_unique_node_id();
     $frequency_id = celerity_generate_unique_node_id();
     $all_day_id = celerity_generate_unique_node_id();
     $start_date_id = celerity_generate_unique_node_id();
     $end_date_id = celerity_generate_unique_node_id();
     $next_workflow = $request->getStr('next');
     $uri_query = $request->getStr('query');
     if ($this->isCreate()) {
         $mode = $request->getStr('mode');
         $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($viewer, $mode);
         $create_start_year = $request->getInt('year');
         $create_start_month = $request->getInt('month');
         $create_start_day = $request->getInt('day');
         $create_start_time = $request->getStr('time');
         if ($create_start_year) {
             $start = AphrontFormDateControlValue::newFromParts($viewer, $create_start_year, $create_start_month, $create_start_day, $create_start_time);
             if (!$start->isValid()) {
                 return new Aphront400Response();
             }
             $start_value = AphrontFormDateControlValue::newFromEpoch($viewer, $start->getEpoch());
             $end = clone $start_value->getDateTime();
             $end->modify('+1 hour');
             $end_value = AphrontFormDateControlValue::newFromEpoch($viewer, $end->format('U'));
         } else {
             list($start_value, $end_value) = $this->getDefaultTimeValues($viewer);
         }
         $recurrence_end_date_value = clone $end_value;
         $recurrence_end_date_value->setOptional(true);
         $submit_label = pht('Create');
         $page_title = pht('Create Event');
         $redirect = 'created';
         $subscribers = array();
         $invitees = array($user_phid);
         $cancel_uri = $this->getApplicationURI();
     } else {
         $event = id(new PhabricatorCalendarEventQuery())->setViewer($viewer)->withIDs(array($this->id))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
         if (!$event) {
             return new Aphront404Response();
         }
         if ($request->getURIData('sequence')) {
             $index = $request->getURIData('sequence');
             $result = $this->getEventAtIndexForGhostPHID($viewer, $event->getPHID(), $index);
             if ($result) {
                 return id(new AphrontRedirectResponse())->setURI('/calendar/event/edit/' . $result->getID() . '/');
             }
             $event = $this->createEventFromGhost($viewer, $event, $index);
             return id(new AphrontRedirectResponse())->setURI('/calendar/event/edit/' . $event->getID() . '/');
         }
         $end_value = AphrontFormDateControlValue::newFromEpoch($viewer, $event->getDateTo());
         $start_value = AphrontFormDateControlValue::newFromEpoch($viewer, $event->getDateFrom());
         $recurrence_end_date_value = id(clone $end_value)->setOptional(true);
         $submit_label = pht('Update');
         $page_title = pht('Update Event');
         $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID($event->getPHID());
         $invitees = array();
         foreach ($event->getInvitees() as $invitee) {
             if ($invitee->isUninvited()) {
                 continue;
             } else {
                 $invitees[] = $invitee->getInviteePHID();
             }
         }
         $cancel_uri = '/' . $event->getMonogram();
     }
     if ($this->isCreate()) {
         $projects = array();
     } else {
         $projects = PhabricatorEdgeQuery::loadDestinationPHIDs($event->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
         $projects = array_reverse($projects);
     }
     $name = $event->getName();
     $description = $event->getDescription();
     $is_all_day = $event->getIsAllDay();
     $is_recurring = $event->getIsRecurring();
     $is_parent = $event->getIsRecurrenceParent();
     $frequency = idx($event->getRecurrenceFrequency(), 'rule');
     $icon = $event->getIcon();
     $edit_policy = $event->getEditPolicy();
     $view_policy = $event->getViewPolicy();
     $space = $event->getSpacePHID();
     if ($request->isFormPost()) {
         $xactions = array();
         $name = $request->getStr('name');
         $start_value = AphrontFormDateControlValue::newFromRequest($request, 'start');
         $end_value = AphrontFormDateControlValue::newFromRequest($request, 'end');
         $recurrence_end_date_value = AphrontFormDateControlValue::newFromRequest($request, 'recurrenceEndDate');
         $recurrence_end_date_value->setOptional(true);
         $projects = $request->getArr('projects');
         $description = $request->getStr('description');
         $subscribers = $request->getArr('subscribers');
         $edit_policy = $request->getStr('editPolicy');
         $view_policy = $request->getStr('viewPolicy');
         $space = $request->getStr('spacePHID');
         $is_recurring = $request->getStr('isRecurring') ? 1 : 0;
         $frequency = $request->getStr('frequency');
         $is_all_day = $request->getStr('isAllDay');
         $icon = $request->getStr('icon');
         $invitees = $request->getArr('invitees');
         $new_invitees = $this->getNewInviteeList($invitees, $event);
         $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
         if ($this->isCreate()) {
             $status = idx($new_invitees, $viewer->getPHID());
             if ($status) {
                 $new_invitees[$viewer->getPHID()] = $status_attending;
             }
         }
         $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_NAME)->setNewValue($name);
         if ($is_recurring && $this->isCreate()) {
             $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_RECURRING)->setNewValue($is_recurring);
             $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_FREQUENCY)->setNewValue(array('rule' => $frequency));
             if (!$recurrence_end_date_value->isDisabled()) {
                 $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE)->setNewValue($recurrence_end_date_value);
             }
         }
         if ($is_recurring && $this->isCreate() || !$is_parent) {
             $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_ALL_DAY)->setNewValue($is_all_day);
             $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_ICON)->setNewValue($icon);
             $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_START_DATE)->setNewValue($start_value);
             $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_END_DATE)->setNewValue($end_value);
         }
         $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS)->setNewValue(array('=' => array_fuse($subscribers)));
         $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_INVITE)->setNewValue($new_invitees);
         $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION)->setNewValue($description);
         $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)->setNewValue($request->getStr('viewPolicy'));
         $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)->setNewValue($request->getStr('editPolicy'));
         $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorTransactions::TYPE_SPACE)->setNewValue($space);
         $editor = id(new PhabricatorCalendarEventEditor())->setActor($viewer)->setContentSourceFromRequest($request)->setContinueOnNoEffect(true);
         try {
             $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
             $xactions[] = id(new PhabricatorCalendarEventTransaction())->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $proj_edge_type)->setNewValue(array('=' => array_fuse($projects)));
             $xactions = $editor->applyTransactions($event, $xactions);
             $response = id(new AphrontRedirectResponse());
             switch ($next_workflow) {
                 case 'day':
                     if (!$uri_query) {
                         $uri_query = 'month';
                     }
                     $year = $start_value->getDateTime()->format('Y');
                     $month = $start_value->getDateTime()->format('m');
                     $day = $start_value->getDateTime()->format('d');
                     $response->setURI('/calendar/query/' . $uri_query . '/' . $year . '/' . $month . '/' . $day . '/');
                     break;
                 default:
                     $response->setURI('/E' . $event->getID());
                     break;
             }
             return $response;
         } catch (PhabricatorApplicationTransactionValidationException $ex) {
             $validation_exception = $ex;
             $error_name = $ex->getShortMessage(PhabricatorCalendarEventTransaction::TYPE_NAME);
             $error_start_date = $ex->getShortMessage(PhabricatorCalendarEventTransaction::TYPE_START_DATE);
             $error_end_date = $ex->getShortMessage(PhabricatorCalendarEventTransaction::TYPE_END_DATE);
             $error_recurrence_end_date = $ex->getShortMessage(PhabricatorCalendarEventTransaction::TYPE_RECURRENCE_END_DATE);
         }
     }
     $is_recurring_checkbox = null;
     $recurrence_end_date_control = null;
     $recurrence_frequency_select = null;
     $all_day_checkbox = null;
     $start_control = null;
     $end_control = null;
     $recurring_date_edit_label = null;
     $current_policies = id(new PhabricatorPolicyQuery())->setViewer($viewer)->setObject($event)->execute();
     $name = id(new AphrontFormTextControl())->setLabel(pht('Name'))->setName('name')->setValue($name)->setError($error_name);
     if ($this->isCreate()) {
         Javelin::initBehavior('recurring-edit', array('isRecurring' => $is_recurring_id, 'frequency' => $frequency_id, 'recurrenceEndDate' => $recurrence_end_date_id));
         $is_recurring_checkbox = id(new AphrontFormCheckboxControl())->addCheckbox('isRecurring', 1, pht('Recurring Event'), $is_recurring, $is_recurring_id);
         $recurrence_end_date_control = id(new AphrontFormDateControl())->setUser($viewer)->setName('recurrenceEndDate')->setLabel(pht('Recurrence End Date'))->setError($error_recurrence_end_date)->setValue($recurrence_end_date_value)->setID($recurrence_end_date_id)->setIsTimeDisabled(true)->setIsDisabled($recurrence_end_date_value->isDisabled())->setAllowNull(true);
         $recurrence_frequency_select = id(new AphrontFormSelectControl())->setName('frequency')->setOptions(array(PhabricatorCalendarEvent::FREQUENCY_DAILY => pht('Daily'), PhabricatorCalendarEvent::FREQUENCY_WEEKLY => pht('Weekly'), PhabricatorCalendarEvent::FREQUENCY_MONTHLY => pht('Monthly'), PhabricatorCalendarEvent::FREQUENCY_YEARLY => pht('Yearly')))->setValue($frequency)->setLabel(pht('Recurring Event Frequency'))->setID($frequency_id)->setDisabled(!$is_recurring);
     }
     if ($this->isCreate() || !$is_parent && !$this->isCreate()) {
         Javelin::initBehavior('event-all-day', array('allDayID' => $all_day_id, 'startDateID' => $start_date_id, 'endDateID' => $end_date_id));
         $all_day_checkbox = id(new AphrontFormCheckboxControl())->addCheckbox('isAllDay', 1, pht('All Day Event'), $is_all_day, $all_day_id);
         $start_control = id(new AphrontFormDateControl())->setUser($viewer)->setName('start')->setLabel(pht('Start'))->setError($error_start_date)->setValue($start_value)->setID($start_date_id)->setIsTimeDisabled($is_all_day)->setEndDateID($end_date_id);
         $end_control = id(new AphrontFormDateControl())->setUser($viewer)->setName('end')->setLabel(pht('End'))->setError($error_end_date)->setValue($end_value)->setID($end_date_id)->setIsTimeDisabled($is_all_day);
     } else {
         if ($is_parent) {
             $recurring_date_edit_label = id(new AphrontFormStaticControl())->setUser($viewer)->setValue(pht('Date and time of recurring event cannot be edited.'));
             if (!$recurrence_end_date_value->isDisabled()) {
                 $disabled_recurrence_end_date_value = $recurrence_end_date_value->getValueAsFormat('M d, Y');
                 $recurrence_end_date_control = id(new AphrontFormStaticControl())->setUser($viewer)->setLabel(pht('Recurrence End Date'))->setValue($disabled_recurrence_end_date_value)->setDisabled(true);
             }
             $recurrence_frequency_select = id(new AphrontFormSelectControl())->setName('frequency')->setOptions(array('daily' => pht('Daily'), 'weekly' => pht('Weekly'), 'monthly' => pht('Monthly'), 'yearly' => pht('Yearly')))->setValue($frequency)->setLabel(pht('Recurring Event Frequency'))->setID($frequency_id)->setDisabled(true);
             $all_day_checkbox = id(new AphrontFormCheckboxControl())->addCheckbox('isAllDay', 1, pht('All Day Event'), $is_all_day, $all_day_id)->setDisabled(true);
             $start_disabled = $start_value->getValueAsFormat('M d, Y, g:i A');
             $end_disabled = $end_value->getValueAsFormat('M d, Y, g:i A');
             $start_control = id(new AphrontFormStaticControl())->setUser($viewer)->setLabel(pht('Start'))->setValue($start_disabled)->setDisabled(true);
             $end_control = id(new AphrontFormStaticControl())->setUser($viewer)->setLabel(pht('End'))->setValue($end_disabled);
         }
     }
     $projects = id(new AphrontFormTokenizerControl())->setLabel(pht('Projects'))->setName('projects')->setValue($projects)->setUser($viewer)->setDatasource(new PhabricatorProjectDatasource());
     $description = id(new PhabricatorRemarkupControl())->setLabel(pht('Description'))->setName('description')->setValue($description)->setUser($viewer);
     $view_policies = id(new AphrontFormPolicyControl())->setUser($viewer)->setValue($view_policy)->setCapability(PhabricatorPolicyCapability::CAN_VIEW)->setPolicyObject($event)->setPolicies($current_policies)->setSpacePHID($space)->setName('viewPolicy');
     $edit_policies = id(new AphrontFormPolicyControl())->setUser($viewer)->setValue($edit_policy)->setCapability(PhabricatorPolicyCapability::CAN_EDIT)->setPolicyObject($event)->setPolicies($current_policies)->setName('editPolicy');
     $subscribers = id(new AphrontFormTokenizerControl())->setLabel(pht('Subscribers'))->setName('subscribers')->setValue($subscribers)->setUser($viewer)->setDatasource(new PhabricatorMetaMTAMailableDatasource());
     $invitees = id(new AphrontFormTokenizerControl())->setLabel(pht('Invitees'))->setName('invitees')->setValue($invitees)->setUser($viewer)->setDatasource(new PhabricatorMetaMTAMailableDatasource());
     if ($this->isCreate()) {
         $icon_uri = $this->getApplicationURI('icon/');
     } else {
         $icon_uri = $this->getApplicationURI('icon/' . $event->getID() . '/');
     }
     $icon_display = PhabricatorCalendarIcon::renderIconForChooser($icon);
     $icon = id(new AphrontFormChooseButtonControl())->setLabel(pht('Icon'))->setName('icon')->setDisplayValue($icon_display)->setButtonText(pht('Choose Icon...'))->setChooseURI($icon_uri)->setValue($icon);
     $form = id(new AphrontFormView())->addHiddenInput('next', $next_workflow)->addHiddenInput('query', $uri_query)->setUser($viewer)->appendChild($name);
     if ($recurring_date_edit_label) {
         $form->appendControl($recurring_date_edit_label);
     }
     if ($is_recurring_checkbox) {
         $form->appendChild($is_recurring_checkbox);
     }
     if ($recurrence_end_date_control) {
         $form->appendChild($recurrence_end_date_control);
     }
     if ($recurrence_frequency_select) {
         $form->appendControl($recurrence_frequency_select);
     }
     $form->appendChild($all_day_checkbox)->appendChild($start_control)->appendChild($end_control)->appendControl($view_policies)->appendControl($edit_policies)->appendControl($subscribers)->appendControl($invitees)->appendChild($projects)->appendChild($description)->appendChild($icon);
     if ($request->isAjax()) {
         return $this->newDialog()->setTitle($page_title)->setWidth(AphrontDialogView::WIDTH_FULL)->appendForm($form)->addCancelButton($cancel_uri)->addSubmitButton($submit_label);
     }
     $submit = id(new AphrontFormSubmitControl())->addCancelButton($cancel_uri)->setValue($submit_label);
     $form->appendChild($submit);
     $form_box = id(new PHUIObjectBoxView())->setHeaderText($page_title)->setForm($form);
     $crumbs = $this->buildApplicationCrumbs();
     if (!$this->isCreate()) {
         $crumbs->addTextCrumb('E' . $event->getId(), '/E' . $event->getId());
     }
     $crumbs->addTextCrumb($page_title);
     $object_box = id(new PHUIObjectBoxView())->setHeaderText($page_title)->setValidationException($validation_exception)->appendChild($form);
     return $this->buildApplicationPage(array($crumbs, $object_box), array('title' => $page_title));
 }
 protected final function importEventDocument(PhabricatorUser $viewer, PhabricatorCalendarImport $import, PhutilCalendarRootNode $root = null)
 {
     $event_type = PhutilCalendarEventNode::NODETYPE;
     $nodes = array();
     if ($root) {
         foreach ($root->getChildren() as $document) {
             foreach ($document->getChildren() as $node) {
                 $node_type = $node->getNodeType();
                 if ($node_type != $event_type) {
                     $import->newLogMessage(PhabricatorCalendarImportIgnoredNodeLogType::LOGTYPE, array('node.type' => $node_type));
                     continue;
                 }
                 $nodes[] = $node;
             }
         }
     }
     // Reject events which have dates outside of the range of a signed
     // 32-bit integer. We'll need to accommodate a wider range of events
     // eventually, but have about 20 years until it's an issue and we'll
     // all be dead by then.
     foreach ($nodes as $key => $node) {
         $dates = array();
         $dates[] = $node->getStartDateTime();
         $dates[] = $node->getEndDateTime();
         $dates[] = $node->getCreatedDateTime();
         $dates[] = $node->getModifiedDateTime();
         $rrule = $node->getRecurrenceRule();
         if ($rrule) {
             $dates[] = $rrule->getUntil();
         }
         $bad_date = false;
         foreach ($dates as $date) {
             if ($date === null) {
                 continue;
             }
             $year = $date->getYear();
             if ($year < 1970 || $year > 2037) {
                 $bad_date = true;
                 break;
             }
         }
         if ($bad_date) {
             $import->newLogMessage(PhabricatorCalendarImportEpochLogType::LOGTYPE, array());
             unset($nodes[$key]);
         }
     }
     // Reject events which occur too frequently. Users do not normally define
     // these events and the UI and application make many assumptions which are
     // incompatible with events recurring once per second.
     foreach ($nodes as $key => $node) {
         $rrule = $node->getRecurrenceRule();
         if (!$rrule) {
             // This is not a recurring event, so we don't need to check the
             // frequency.
             continue;
         }
         $scale = $rrule->getFrequencyScale();
         if ($scale >= PhutilCalendarRecurrenceRule::SCALE_DAILY) {
             // This is a daily, weekly, monthly, or yearly event. These are
             // supported.
         } else {
             // This is an hourly, minutely, or secondly event.
             $import->newLogMessage(PhabricatorCalendarImportFrequencyLogType::LOGTYPE, array('frequency' => $rrule->getFrequency()));
             unset($nodes[$key]);
         }
     }
     $node_map = array();
     foreach ($nodes as $node) {
         $full_uid = $this->getFullNodeUID($node);
         if (isset($node_map[$full_uid])) {
             $import->newLogMessage(PhabricatorCalendarImportDuplicateLogType::LOGTYPE, array('uid.full' => $full_uid));
             continue;
         }
         $node_map[$full_uid] = $node;
     }
     // If we already know about some of these events and they were created
     // here, we're not going to import it again. This can happen if a user
     // exports an event and then tries to import it again. This is probably
     // not what they meant to do and this pathway generally leads to madness.
     $likely_phids = array();
     foreach ($node_map as $full_uid => $node) {
         $uid = $node->getUID();
         $matches = null;
         if (preg_match('/^(PHID-.*)@(.*)\\z/', $uid, $matches)) {
             $likely_phids[$full_uid] = $matches[1];
         }
     }
     if ($likely_phids) {
         // NOTE: We're using the omnipotent viewer here because we don't want
         // to collide with events that already exist, even if you can't see
         // them.
         $events = id(new PhabricatorCalendarEventQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withPHIDs($likely_phids)->execute();
         $events = mpull($events, null, 'getPHID');
         foreach ($node_map as $full_uid => $node) {
             $phid = idx($likely_phids, $full_uid);
             if (!$phid) {
                 continue;
             }
             $event = idx($events, $phid);
             if (!$event) {
                 continue;
             }
             $import->newLogMessage(PhabricatorCalendarImportOriginalLogType::LOGTYPE, array('phid' => $event->getPHID()));
             unset($node_map[$full_uid]);
         }
     }
     if ($node_map) {
         $events = id(new PhabricatorCalendarEventQuery())->setViewer($viewer)->withImportAuthorPHIDs(array($import->getAuthorPHID()))->withImportUIDs(array_keys($node_map))->execute();
         $events = mpull($events, null, 'getImportUID');
     } else {
         $events = null;
     }
     $xactions = array();
     $update_map = array();
     $invitee_map = array();
     $attendee_map = array();
     foreach ($node_map as $full_uid => $node) {
         $event = idx($events, $full_uid);
         if (!$event) {
             $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($viewer);
         }
         $event->setImportAuthorPHID($import->getAuthorPHID())->setImportSourcePHID($import->getPHID())->setImportUID($full_uid)->attachImportSource($import);
         $this->updateEventFromNode($viewer, $event, $node);
         $xactions[$full_uid] = $this->newUpdateTransactions($event, $node);
         $update_map[$full_uid] = $event;
         $attendee_map[$full_uid] = array();
         $attendees = $node->getAttendees();
         $private_index = 1;
         foreach ($attendees as $attendee) {
             // Generate a "name" for this attendee which is not an email address.
             // We avoid disclosing email addresses to be consistent with the rest
             // of the product.
             $name = $attendee->getName();
             if (preg_match('/@/', $name)) {
                 $name = new PhutilEmailAddress($name);
                 $name = $name->getDisplayName();
             }
             // If we don't have a name or the name still looks like it's an
             // email address, give them a dummy placeholder name.
             if (!strlen($name) || preg_match('/@/', $name)) {
                 $name = pht('Private User %d', $private_index);
                 $private_index++;
             }
             $attendee_map[$full_uid][$name] = $attendee;
         }
     }
     $attendee_names = array();
     foreach ($attendee_map as $full_uid => $event_attendees) {
         foreach ($event_attendees as $name => $attendee) {
             $attendee_names[$name] = $attendee;
         }
     }
     if ($attendee_names) {
         $external_invitees = id(new PhabricatorCalendarExternalInviteeQuery())->setViewer($viewer)->withNames(array_keys($attendee_names))->execute();
         $external_invitees = mpull($external_invitees, null, 'getName');
         foreach ($attendee_names as $name => $attendee) {
             if (isset($external_invitees[$name])) {
                 continue;
             }
             $external_invitee = id(new PhabricatorCalendarExternalInvitee())->setName($name)->setURI($attendee->getURI())->setSourcePHID($import->getPHID());
             try {
                 $external_invitee->save();
             } catch (AphrontDuplicateKeyQueryException $ex) {
                 $external_invitee = id(new PhabricatorCalendarExternalInviteeQuery())->setViewer($viewer)->withNames(array($name))->executeOne();
             }
             $external_invitees[$name] = $external_invitee;
         }
     }
     // Reorder events so we create parents first. This allows us to populate
     // "instanceOfEventPHID" correctly.
     $insert_order = array();
     foreach ($update_map as $full_uid => $event) {
         $parent_uid = $this->getParentNodeUID($node_map[$full_uid]);
         if ($parent_uid === null) {
             $insert_order[$full_uid] = $full_uid;
             continue;
         }
         if (empty($update_map[$parent_uid])) {
             // The parent was not present in this import, which means it either
             // does not exist or we're going to delete it anyway. We just drop
             // this node.
             $import->newLogMessage(PhabricatorCalendarImportOrphanLogType::LOGTYPE, array('uid.full' => $full_uid, 'uid.parent' => $parent_uid));
             continue;
         }
         // Otherwise, we're going to insert the parent first, then insert
         // the child.
         $insert_order[$parent_uid] = $parent_uid;
         $insert_order[$full_uid] = $full_uid;
     }
     // TODO: Define per-engine content sources so this can say "via Upload" or
     // whatever.
     $content_source = PhabricatorContentSource::newForSource(PhabricatorWebContentSource::SOURCECONST);
     // NOTE: We're using the omnipotent user here because imported events are
     // otherwise immutable.
     $edit_actor = PhabricatorUser::getOmnipotentUser();
     $update_map = array_select_keys($update_map, $insert_order);
     foreach ($update_map as $full_uid => $event) {
         $parent_uid = $this->getParentNodeUID($node_map[$full_uid]);
         if ($parent_uid) {
             $parent_phid = $update_map[$parent_uid]->getPHID();
         } else {
             $parent_phid = null;
         }
         $event->setInstanceOfEventPHID($parent_phid);
         $event_xactions = $xactions[$full_uid];
         $editor = id(new PhabricatorCalendarEventEditor())->setActor($edit_actor)->setActingAsPHID($import->getPHID())->setContentSource($content_source)->setContinueOnNoEffect(true)->setContinueOnMissingFields(true);
         $is_new = !$event->getID();
         $editor->applyTransactions($event, $event_xactions);
         // We're just forcing attendees to the correct values here because
         // transactions intentionally don't let you RSVP for other users. This
         // might need to be turned into a special type of transaction eventually.
         $attendees = $attendee_map[$full_uid];
         $old_map = $event->getInvitees();
         $old_map = mpull($old_map, null, 'getInviteePHID');
         $new_map = array();
         foreach ($attendees as $name => $attendee) {
             $phid = $external_invitees[$name]->getPHID();
             $invitee = idx($old_map, $phid);
             if (!$invitee) {
                 $invitee = id(new PhabricatorCalendarEventInvitee())->setEventPHID($event->getPHID())->setInviteePHID($phid)->setInviterPHID($import->getPHID());
             }
             switch ($attendee->getStatus()) {
                 case PhutilCalendarUserNode::STATUS_ACCEPTED:
                     $status = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
                     break;
                 case PhutilCalendarUserNode::STATUS_DECLINED:
                     $status = PhabricatorCalendarEventInvitee::STATUS_DECLINED;
                     break;
                 case PhutilCalendarUserNode::STATUS_INVITED:
                 default:
                     $status = PhabricatorCalendarEventInvitee::STATUS_INVITED;
                     break;
             }
             $invitee->setStatus($status);
             $invitee->save();
             $new_map[$phid] = $invitee;
         }
         foreach ($old_map as $phid => $invitee) {
             if (empty($new_map[$phid])) {
                 $invitee->delete();
             }
         }
         $event->attachInvitees($new_map);
         $import->newLogMessage(PhabricatorCalendarImportUpdateLogType::LOGTYPE, array('new' => $is_new, 'phid' => $event->getPHID()));
     }
     if (!$update_map) {
         $import->newLogMessage(PhabricatorCalendarImportEmptyLogType::LOGTYPE, array());
     }
     // Delete any events which are no longer present in the source.
     $updated_events = mpull($update_map, null, 'getPHID');
     $source_events = id(new PhabricatorCalendarEventQuery())->setViewer($viewer)->withImportSourcePHIDs(array($import->getPHID()))->execute();
     $engine = new PhabricatorDestructionEngine();
     foreach ($source_events as $source_event) {
         if (isset($updated_events[$source_event->getPHID()])) {
             // We imported and updated this event, so keep it around.
             continue;
         }
         $import->newLogMessage(PhabricatorCalendarImportDeleteLogType::LOGTYPE, array('name' => $source_event->getName()));
         $engine->destroyObject($source_event);
     }
 }