From 65d9c0ea24f7959937135863be45e408d470eec6 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Sun, 31 May 2015 15:04:48 -0700 Subject: [PATCH] Edit recurring ghost events, not recurring event. Summary: Closes T8358, Edit recurring ghost events, not recurring event. Test Plan: Create recurring event, original event should not be editable, but ghost events should be editable. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8358 Differential Revision: https://secure.phabricator.com/D13075 --- .../PhabricatorCalendarApplication.php | 2 +- ...PhabricatorCalendarEventEditController.php | 46 +++++++++++++++ ...PhabricatorCalendarEventViewController.php | 16 ++++- .../query/PhabricatorCalendarEventQuery.php | 58 ++++++++++++++++++- .../storage/PhabricatorCalendarEvent.php | 7 ++- 5 files changed, 122 insertions(+), 7 deletions(-) diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 465bb2ae5c..38bd7f9d0a 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -53,7 +53,7 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { '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 index 0710b56907..489b4e6d25 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -86,6 +86,52 @@ final class PhabricatorCalendarEventEditController 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 index 07f2762f63..3ef692b9a8 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -135,7 +135,19 @@ final class PhabricatorCalendarEventViewController $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 @@ final class PhabricatorCalendarEventViewController $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 index 4ed222b3d7..23dd2a2cf9 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -10,6 +10,8 @@ final class PhabricatorCalendarEventQuery private $inviteePHIDs; private $creatorPHIDs; private $isCancelled; + private $instanceSequencePairs; + private $generateGhosts = false; @@ -49,6 +51,11 @@ final class PhabricatorCalendarEventQuery return $this; } + public function withInstanceSequencePairs(array $pairs) { + $this->instanceSequencePairs = $pairs; + return $this; + } + protected function getDefaultOrderVector() { return array('start', 'id'); } @@ -98,12 +105,15 @@ final class PhabricatorCalendarEventQuery 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 @@ final class PhabricatorCalendarEventQuery $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 @@ final class PhabricatorCalendarEventQuery (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 index 3207e9897f..979fec1444 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -203,6 +203,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO '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 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO 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) {