diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -53,7 +53,7 @@ 'event/' => array( 'create/' => 'PhabricatorCalendarEventEditController', - 'edit/(?P[1-9]\d*)/' + 'edit/(?P[1-9]\d*)/(?:(?P\d+)/)?' => 'PhabricatorCalendarEventEditController', 'drag/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventDragController', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -86,6 +86,52 @@ return new Aphront404Response(); } + if ($request->getURIData('sequence')) { + $index = $request->getURIData('sequence'); + + $result = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withInstanceSequencePairs( + array( + array( + $event->getPHID(), + $index, + ), + )) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if ($result) { + return id(new AphrontRedirectResponse()) + ->setURI('/calendar/event/edit/'.$result->getID().'/'); + } + + $invitees = $event->getInvitees(); + + $new_ghost = $event->generateNthGhost($index, $viewer); + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $new_ghost + ->setID(null) + ->setPHID(null) + ->removeViewerTimezone($viewer) + ->save(); + $ghost_invitees = array(); + foreach ($invitees as $invitee) { + $ghost_invitee = clone $invitee; + $ghost_invitee + ->setID(null) + ->setEventPHID($new_ghost->getPHID()) + ->save(); + } + unset($unguarded); + return id(new AphrontRedirectResponse()) + ->setURI('/calendar/event/edit/'.$new_ghost->getID().'/'); + } + $end_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $event->getDateTo()); diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -135,7 +135,19 @@ $event, PhabricatorPolicyCapability::CAN_EDIT); - if (!$event->getIsGhostEvent()) { + if (($event->getIsRecurring() && $event->getIsGhostEvent())) { + $index = $event->getSequenceIndex(); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit This Instance')) + ->setIcon('fa-pencil') + ->setHref($this->getApplicationURI("event/edit/{$id}/{$index}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + } + + if (!$event->getIsRecurring() && !$event->getIsGhostEvent()) { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Event')) @@ -219,7 +231,7 @@ $properties->addProperty( pht('Recurs'), ucwords(idx($event->getRecurrenceFrequency(), 'rule'))); - if ($event->getIsGhostEvent()) { + if ($event->getInstanceOfEventPHID()) { $properties->addProperty( pht('Recurrence of Event'), $viewer->renderHandle($event->getInstanceOfEventPHID())); diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -10,6 +10,8 @@ private $inviteePHIDs; private $creatorPHIDs; private $isCancelled; + private $instanceSequencePairs; + private $generateGhosts = false; @@ -49,6 +51,11 @@ return $this; } + public function withInstanceSequencePairs(array $pairs) { + $this->instanceSequencePairs = $pairs; + return $this; + } + protected function getDefaultOrderVector() { return array('start', 'id'); } @@ -98,12 +105,15 @@ return $events; } + $map = array(); + $instance_sequence_pairs = array(); + foreach ($events as $event) { $sequence_start = 0; $instance_count = null; $duration = $event->getDateTo() - $event->getDateFrom(); - if ($event->getIsRecurring()) { + if ($event->getIsRecurring() && !$event->getInstanceOfEventPHID()) { $frequency = $event->getFrequencyUnit(); $modify_key = '+1 '.$frequency; @@ -147,7 +157,37 @@ $max_sequence = $sequence_start + $instance_count; for ($index = $sequence_start; $index < $max_sequence; $index++) { + $instance_sequence_pairs[] = array($event->getPHID(), $index); $events[] = $event->generateNthGhost($index, $viewer); + + $key = last_key($events); + $map[$event->getPHID()][$index] = $key; + } + } + } + + if (count($instance_sequence_pairs) > 0) { + $sub_query = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->setParentQuery($this) + ->withInstanceSequencePairs($instance_sequence_pairs) + ->execute(); + + foreach ($sub_query as $edited_ghost) { + $indexes = idx($map, $edited_ghost->getInstanceOfEventPHID()); + $key = idx($indexes, $edited_ghost->getSequenceIndex()); + $events[$key] = $edited_ghost; + } + + $id_map = array(); + foreach ($events as $key => $event) { + if ($event->getIsGhostEvent()) { + continue; + } + if (isset($id_map[$event->getID()])) { + unset($events[$key]); + } else { + $id_map[$event->getID()] = true; } } } @@ -220,6 +260,22 @@ (int)$this->isCancelled); } + if ($this->instanceSequencePairs !== null) { + $sql = array(); + + foreach ($this->instanceSequencePairs as $pair) { + $sql[] = qsprintf( + $conn_r, + '(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)', + $pair[0], + $pair[1]); + } + $where[] = qsprintf( + $conn_r, + '%Q', + implode(' OR ', $sql)); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -203,6 +203,10 @@ 'userPHID_dateFrom' => array( 'columns' => array('userPHID', 'dateTo'), ), + 'key_instance' => array( + 'columns' => array('instanceOfEventPHID', 'sequenceIndex'), + 'unique' => true, + ), ), self::CONFIG_SERIALIZATION => array( 'recurrenceFrequency' => self::SERIALIZATION_JSON, @@ -391,9 +395,6 @@ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { // The owner of a task can always view and edit it. $user_phid = $this->getUserPHID(); - if ($this->isGhostEvent) { - return false; - } if ($user_phid) { $viewer_phid = $viewer->getPHID(); if ($viewer_phid == $user_phid) {