@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+122 -7
+1 -1
src/applications/calendar/application/PhabricatorCalendarApplication.php
··· 53 53 'event/' => array( 54 54 'create/' 55 55 => 'PhabricatorCalendarEventEditController', 56 - 'edit/(?P<id>[1-9]\d*)/' 56 + 'edit/(?P<id>[1-9]\d*)/(?:(?P<sequence>\d+)/)?' 57 57 => 'PhabricatorCalendarEventEditController', 58 58 'drag/(?P<id>[1-9]\d*)/' 59 59 => 'PhabricatorCalendarEventDragController',
+46
src/applications/calendar/controller/PhabricatorCalendarEventEditController.php
··· 86 86 return new Aphront404Response(); 87 87 } 88 88 89 + if ($request->getURIData('sequence')) { 90 + $index = $request->getURIData('sequence'); 91 + 92 + $result = id(new PhabricatorCalendarEventQuery()) 93 + ->setViewer($viewer) 94 + ->withInstanceSequencePairs( 95 + array( 96 + array( 97 + $event->getPHID(), 98 + $index, 99 + ), 100 + )) 101 + ->requireCapabilities( 102 + array( 103 + PhabricatorPolicyCapability::CAN_VIEW, 104 + PhabricatorPolicyCapability::CAN_EDIT, 105 + )) 106 + ->executeOne(); 107 + 108 + if ($result) { 109 + return id(new AphrontRedirectResponse()) 110 + ->setURI('/calendar/event/edit/'.$result->getID().'/'); 111 + } 112 + 113 + $invitees = $event->getInvitees(); 114 + 115 + $new_ghost = $event->generateNthGhost($index, $viewer); 116 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 117 + $new_ghost 118 + ->setID(null) 119 + ->setPHID(null) 120 + ->removeViewerTimezone($viewer) 121 + ->save(); 122 + $ghost_invitees = array(); 123 + foreach ($invitees as $invitee) { 124 + $ghost_invitee = clone $invitee; 125 + $ghost_invitee 126 + ->setID(null) 127 + ->setEventPHID($new_ghost->getPHID()) 128 + ->save(); 129 + } 130 + unset($unguarded); 131 + return id(new AphrontRedirectResponse()) 132 + ->setURI('/calendar/event/edit/'.$new_ghost->getID().'/'); 133 + } 134 + 89 135 $end_value = AphrontFormDateControlValue::newFromEpoch( 90 136 $viewer, 91 137 $event->getDateTo());
+14 -2
src/applications/calendar/controller/PhabricatorCalendarEventViewController.php
··· 135 135 $event, 136 136 PhabricatorPolicyCapability::CAN_EDIT); 137 137 138 - if (!$event->getIsGhostEvent()) { 138 + if (($event->getIsRecurring() && $event->getIsGhostEvent())) { 139 + $index = $event->getSequenceIndex(); 140 + 141 + $actions->addAction( 142 + id(new PhabricatorActionView()) 143 + ->setName(pht('Edit This Instance')) 144 + ->setIcon('fa-pencil') 145 + ->setHref($this->getApplicationURI("event/edit/{$id}/{$index}/")) 146 + ->setDisabled(!$can_edit) 147 + ->setWorkflow(!$can_edit)); 148 + } 149 + 150 + if (!$event->getIsRecurring() && !$event->getIsGhostEvent()) { 139 151 $actions->addAction( 140 152 id(new PhabricatorActionView()) 141 153 ->setName(pht('Edit Event')) ··· 219 231 $properties->addProperty( 220 232 pht('Recurs'), 221 233 ucwords(idx($event->getRecurrenceFrequency(), 'rule'))); 222 - if ($event->getIsGhostEvent()) { 234 + if ($event->getInstanceOfEventPHID()) { 223 235 $properties->addProperty( 224 236 pht('Recurrence of Event'), 225 237 $viewer->renderHandle($event->getInstanceOfEventPHID()));
+57 -1
src/applications/calendar/query/PhabricatorCalendarEventQuery.php
··· 10 10 private $inviteePHIDs; 11 11 private $creatorPHIDs; 12 12 private $isCancelled; 13 + private $instanceSequencePairs; 14 + 13 15 14 16 private $generateGhosts = false; 15 17 ··· 46 48 47 49 public function withIsCancelled($is_cancelled) { 48 50 $this->isCancelled = $is_cancelled; 51 + return $this; 52 + } 53 + 54 + public function withInstanceSequencePairs(array $pairs) { 55 + $this->instanceSequencePairs = $pairs; 49 56 return $this; 50 57 } 51 58 ··· 98 105 return $events; 99 106 } 100 107 108 + $map = array(); 109 + $instance_sequence_pairs = array(); 110 + 101 111 foreach ($events as $event) { 102 112 $sequence_start = 0; 103 113 $instance_count = null; 104 114 $duration = $event->getDateTo() - $event->getDateFrom(); 105 115 106 - if ($event->getIsRecurring()) { 116 + if ($event->getIsRecurring() && !$event->getInstanceOfEventPHID()) { 107 117 $frequency = $event->getFrequencyUnit(); 108 118 $modify_key = '+1 '.$frequency; 109 119 ··· 147 157 148 158 $max_sequence = $sequence_start + $instance_count; 149 159 for ($index = $sequence_start; $index < $max_sequence; $index++) { 160 + $instance_sequence_pairs[] = array($event->getPHID(), $index); 150 161 $events[] = $event->generateNthGhost($index, $viewer); 162 + 163 + $key = last_key($events); 164 + $map[$event->getPHID()][$index] = $key; 165 + } 166 + } 167 + } 168 + 169 + if (count($instance_sequence_pairs) > 0) { 170 + $sub_query = id(new PhabricatorCalendarEventQuery()) 171 + ->setViewer($viewer) 172 + ->setParentQuery($this) 173 + ->withInstanceSequencePairs($instance_sequence_pairs) 174 + ->execute(); 175 + 176 + foreach ($sub_query as $edited_ghost) { 177 + $indexes = idx($map, $edited_ghost->getInstanceOfEventPHID()); 178 + $key = idx($indexes, $edited_ghost->getSequenceIndex()); 179 + $events[$key] = $edited_ghost; 180 + } 181 + 182 + $id_map = array(); 183 + foreach ($events as $key => $event) { 184 + if ($event->getIsGhostEvent()) { 185 + continue; 186 + } 187 + if (isset($id_map[$event->getID()])) { 188 + unset($events[$key]); 189 + } else { 190 + $id_map[$event->getID()] = true; 151 191 } 152 192 } 153 193 } ··· 218 258 $conn_r, 219 259 'event.isCancelled = %d', 220 260 (int)$this->isCancelled); 261 + } 262 + 263 + if ($this->instanceSequencePairs !== null) { 264 + $sql = array(); 265 + 266 + foreach ($this->instanceSequencePairs as $pair) { 267 + $sql[] = qsprintf( 268 + $conn_r, 269 + '(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)', 270 + $pair[0], 271 + $pair[1]); 272 + } 273 + $where[] = qsprintf( 274 + $conn_r, 275 + '%Q', 276 + implode(' OR ', $sql)); 221 277 } 222 278 223 279 $where[] = $this->buildPagingClause($conn_r);
+4 -3
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 203 203 'userPHID_dateFrom' => array( 204 204 'columns' => array('userPHID', 'dateTo'), 205 205 ), 206 + 'key_instance' => array( 207 + 'columns' => array('instanceOfEventPHID', 'sequenceIndex'), 208 + 'unique' => true, 209 + ), 206 210 ), 207 211 self::CONFIG_SERIALIZATION => array( 208 212 'recurrenceFrequency' => self::SERIALIZATION_JSON, ··· 391 395 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 392 396 // The owner of a task can always view and edit it. 393 397 $user_phid = $this->getUserPHID(); 394 - if ($this->isGhostEvent) { 395 - return false; 396 - } 397 398 if ($user_phid) { 398 399 $viewer_phid = $viewer->getPHID(); 399 400 if ($viewer_phid == $user_phid) {