Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15417458
D16778.id40406.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
D16778.id40406.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2049,6 +2049,7 @@
'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php',
'PhabricatorCalendarEventEndDateTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventEndDateTransaction.php',
'PhabricatorCalendarEventExportController' => 'applications/calendar/controller/PhabricatorCalendarEventExportController.php',
+ 'PhabricatorCalendarEventForkTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php',
'PhabricatorCalendarEventFrequencyTransaction' => 'applications/calendar/xaction/PhabricatorCalendarEventFrequencyTransaction.php',
'PhabricatorCalendarEventFulltextEngine' => 'applications/calendar/search/PhabricatorCalendarEventFulltextEngine.php',
'PhabricatorCalendarEventHeraldAdapter' => 'applications/calendar/herald/PhabricatorCalendarEventHeraldAdapter.php',
@@ -6889,6 +6890,7 @@
'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand',
'PhabricatorCalendarEventEndDateTransaction' => 'PhabricatorCalendarEventDateTransaction',
'PhabricatorCalendarEventExportController' => 'PhabricatorCalendarController',
+ 'PhabricatorCalendarEventForkTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventFrequencyTransaction' => 'PhabricatorCalendarEventTransactionType',
'PhabricatorCalendarEventFulltextEngine' => 'PhabricatorFulltextEngine',
'PhabricatorCalendarEventHeraldAdapter' => 'HeraldAdapter',
diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php
--- a/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarEventCancelController.php
@@ -32,88 +32,152 @@
$is_parent = $event->isParentEvent();
$is_child = $event->isChildEvent();
- $is_cancelled = $event->getIsCancelled();
- if ($is_child) {
- $is_parent_cancelled = $event->getParentEvent()->getIsCancelled();
- } else {
- $is_parent_cancelled = false;
- }
+ $is_cancelled = $event->getIsCancelled();
+ $is_recurring = $event->getIsRecurring();
$validation_exception = null;
if ($request->isFormPost()) {
- $xactions = array();
- $xaction = id(new PhabricatorCalendarEventTransaction())
- ->setTransactionType(
- PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE)
- ->setNewValue(!$is_cancelled);
+ $targets = array();
+ if ($is_recurring) {
+ $mode = $request->getStr('mode');
+ $is_future = ($mode == 'future');
+
+ // We need to fork the event if we're cancelling just the parent, or
+ // are cancelling a child and all future events.
+ $must_fork = ($is_child && $is_future) ||
+ ($is_parent && !$is_future);
+
+ if ($must_fork) {
+ if ($is_child) {
+ $xactions = array();
- $editor = id(new PhabricatorCalendarEventEditor())
- ->setActor($viewer)
- ->setContentSourceFromRequest($request)
- ->setContinueOnNoEffect(true)
- ->setContinueOnMissingFields(true);
+ $xaction = id(new PhabricatorCalendarEventTransaction())
+ ->setTransactionType(
+ PhabricatorCalendarEventForkTransaction::TRANSACTIONTYPE)
+ ->setNewValue(true);
- try {
- $editor->applyTransactions($event, array($xaction));
+ $editor = id(new PhabricatorCalendarEventEditor())
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true)
+ ->setContinueOnMissingFields(true);
+
+ $editor->applyTransactions($event, array($xaction));
+
+ $targets[] = $event;
+ } else {
+ // TODO: This is a huge mess; we need to load or generate the
+ // first child, then fork that, then apply the event to the
+ // parent. Just bail for now.
+ throw new Exception(
+ pht(
+ 'Individual edits to parent events are not yet supported '.
+ 'because they are real tricky to implement.'));
+ }
+ } else {
+ $targets[] = $event;
+ }
+
+ if ($is_future) {
+ // NOTE: If you can't edit some of the future events, we just
+ // don't try to update them. This seems like it's probably what
+ // users are likely to expect.
+ $future = id(new PhabricatorCalendarEventQuery())
+ ->setViewer($viewer)
+ ->withParentEventPHIDs(array($event->getPHID()))
+ ->withUTCInitialEpochBetween($event->getUTCInitialEpoch(), null)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->execute();
+ foreach ($future as $future_event) {
+ $targets[] = $future_event;
+ }
+ }
+ } else {
+ $targets = array($event);
+ }
+
+ foreach ($targets as $target) {
+ $xactions = array();
+
+ $xaction = id(new PhabricatorCalendarEventTransaction())
+ ->setTransactionType(
+ PhabricatorCalendarEventCancelTransaction::TRANSACTIONTYPE)
+ ->setNewValue(!$is_cancelled);
+
+ $editor = id(new PhabricatorCalendarEventEditor())
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true)
+ ->setContinueOnMissingFields(true);
+
+ try {
+ $editor->applyTransactions($target, array($xaction));
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ $validation_exception = $ex;
+ break;
+ }
+
+ }
+
+ if (!$validation_exception) {
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
- } catch (PhabricatorApplicationTransactionValidationException $ex) {
- $validation_exception = $ex;
}
}
if ($is_cancelled) {
- if ($is_parent_cancelled) {
- $title = pht('Cannot Reinstate Instance');
- $paragraph = pht(
- 'You cannot reinstate an instance of a cancelled recurring event.');
- $cancel = pht('Back');
- $submit = null;
- } else if ($is_child) {
- $title = pht('Reinstate Instance');
- $paragraph = pht(
- 'Reinstate this instance of this recurring event?');
- $cancel = pht('Back');
- $submit = pht('Reinstate Instance');
- } else if ($is_parent) {
- $title = pht('Reinstate Recurring Event');
- $paragraph = pht(
- 'Reinstate all instances of this recurring event which have not '.
- 'been individually cancelled?');
- $cancel = pht('Back');
- $submit = pht('Reinstate Recurring Event');
+ $title = pht('Reinstate Event');
+ if ($is_recurring) {
+ $body = pht(
+ 'This event is part of a series. Which events do you want to '.
+ 'reinstate?');
+ $show_control = true;
} else {
- $title = pht('Reinstate Event');
- $paragraph = pht('Reinstate this event?');
- $cancel = pht('Back');
- $submit = pht('Reinstate Event');
+ $body = pht('Reinstate this event?');
+ $show_control = false;
}
+ $submit = pht('Reinstate Event');
} else {
- if ($is_child) {
- $title = pht('Cancel Instance');
- $paragraph = pht('Cancel this instance of this recurring event?');
- $cancel = pht('Back');
- $submit = pht('Cancel Instance');
- } else if ($is_parent) {
- $title = pht('Cancel Recurrin Event');
- $paragraph = pht('Cancel this entire series of recurring events?');
- $cancel = pht('Back');
- $submit = pht('Cancel Recurring Event');
+ $title = pht('Cancel Event');
+ if ($is_recurring) {
+ $body = pht(
+ 'This event is part of a series. Which events do you want to '.
+ 'cancel?');
+ $show_control = true;
} else {
- $title = pht('Cancel Event');
- $paragraph = pht(
- 'Cancel this event? You can always reinstate the event later.');
- $cancel = pht('Back');
- $submit = pht('Cancel Event');
+ $body = pht('Cancel this event?');
+ $show_control = false;
}
+ $submit = pht('Cancel Event');
}
- return $this->newDialog()
+ $dialog = $this->newDialog()
->setTitle($title)
->setValidationException($validation_exception)
- ->appendParagraph($paragraph)
- ->addCancelButton($cancel_uri, $cancel)
+ ->appendParagraph($body)
+ ->addCancelButton($cancel_uri, pht('Back'))
->addSubmitButton($submit);
+
+ if ($show_control) {
+ $form = id(new AphrontFormView())
+ ->setViewer($viewer)
+ ->appendControl(
+ id(new AphrontFormSelectControl())
+ ->setLabel(pht('Cancel Events'))
+ ->setName('mode')
+ ->setOptions(
+ array(
+ 'this' => pht('Only This Event'),
+ 'future' => pht('All Future Events'),
+ )));
+ $dialog->appendForm($form);
+ }
+
+ return $dialog;
}
}
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
@@ -206,20 +206,8 @@
$cancel_uri = $this->getApplicationURI("event/cancel/{$id}/");
$cancel_disabled = !$can_edit;
- if ($event->isChildEvent()) {
- $cancel_label = pht('Cancel This Instance');
- $reinstate_label = pht('Reinstate This Instance');
-
- if ($event->getParentEvent()->getIsCancelled()) {
- $cancel_disabled = true;
- }
- } else if ($event->isParentEvent()) {
- $cancel_label = pht('Cancel All');
- $reinstate_label = pht('Reinstate All');
- } else {
- $cancel_label = pht('Cancel Event');
- $reinstate_label = pht('Reinstate Event');
- }
+ $cancel_label = pht('Cancel Event');
+ $reinstate_label = pht('Reinstate Event');
if ($event->isCancelledEvent()) {
$curtain->addAction(
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
@@ -17,6 +17,8 @@
private $importSourcePHIDs;
private $importAuthorPHIDs;
private $importUIDs;
+ private $utcInitialEpochMin;
+ private $utcInitialEpochMax;
private $generateGhosts = false;
@@ -45,6 +47,12 @@
return $this;
}
+ public function withUTCInitialEpochBetween($min, $max) {
+ $this->utcInitialEpochMin = $min;
+ $this->utcInitialEpochMax = $max;
+ return $this;
+ }
+
public function withInvitedPHIDs(array $phids) {
$this->inviteePHIDs = $phids;
return $this;
@@ -371,6 +379,20 @@
$this->rangeEnd + phutil_units('16 hours in seconds'));
}
+ if ($this->utcInitialEpochMin !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'event.utcInitialEpoch >= %d',
+ $this->utcInitialEpochMin);
+ }
+
+ if ($this->utcInitialEpochMax !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'event.utcInitialEpoch <= %d',
+ $this->utcInitialEpochMax);
+ }
+
if ($this->inviteePHIDs !== null) {
$where[] = qsprintf(
$conn,
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
@@ -165,6 +165,7 @@
'editPolicy' => true,
'name' => true,
'description' => true,
+ 'isCancelled' => true,
);
// Read these fields from the parent event instead of this event. For
@@ -204,7 +205,8 @@
->setViewPolicy($parent->getViewPolicy())
->setEditPolicy($parent->getEditPolicy())
->setName($parent->getName())
- ->setDescription($parent->getDescription());
+ ->setDescription($parent->getDescription())
+ ->setIsCancelled($parent->getIsCancelled());
if ($start) {
$start_datetime = $start;
@@ -569,7 +571,7 @@
return $this->assertAttached($this->parentEvent);
}
- public function attachParentEvent($event) {
+ public function attachParentEvent(PhabricatorCalendarEvent $event = null) {
$this->parentEvent = $event;
return $this;
}
@@ -583,17 +585,8 @@
}
public function isCancelledEvent() {
- if ($this->getIsCancelled()) {
- return true;
- }
-
- if ($this->isChildEvent()) {
- if ($this->getParentEvent()->getIsCancelled()) {
- return true;
- }
- }
-
- return false;
+ // TODO: Remove this wrapper.
+ return $this->getIsCancelled();
}
public function renderEventDate(
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php
@@ -0,0 +1,59 @@
+<?php
+
+final class PhabricatorCalendarEventForkTransaction
+ extends PhabricatorCalendarEventTransactionType {
+
+ const TRANSACTIONTYPE = 'calendar.fork';
+
+ public function generateOldValue($object) {
+ return false;
+ }
+
+ public function shouldHide() {
+ // This transaction is purely an internal implementation detail which
+ // supports editing groups of events like "All Future Events".
+ return true;
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $parent = $object->getParentEvent();
+
+ $object->setInstanceOfEventPHID(null);
+ $object->attachParentEvent(null);
+
+ $rrule = $parent->newRecurrenceRule();
+ $object->setRecurrenceRule($rrule);
+
+ $until = $parent->newUntilDateTime();
+ if ($until) {
+ $object->setUntilDateTime($until);
+ }
+
+ $old_sequence_index = $object->getSequenceIndex();
+ $object->setSequenceIndex(0);
+
+ // Stop the parent event from recurring after the start date of this event.
+ $parent->setUntilDateTime($object->newStartDateTime());
+ $parent->save();
+
+ // NOTE: If we implement "COUNT" on editable events, we need to adjust
+ // the "COUNT" here and divide it up between the parent and the fork.
+
+ // Make all following children of the old parent children of this node
+ // instead.
+ $conn = $object->establishConnection('w');
+ queryfx(
+ $conn,
+ 'UPDATE %T SET
+ instanceOfEventPHID = %s,
+ sequenceIndex = (sequenceIndex - %d)
+ WHERE instanceOfEventPHID = %s
+ AND utcInstanceEpoch > %d',
+ $object->getTableName(),
+ $object->getPHID(),
+ $old_sequence_index,
+ $parent->getPHID(),
+ $object->getUTCInstanceEpoch());
+ }
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mar 21 2025, 5:06 PM (4 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7390750
Default Alt Text
D16778.id40406.diff (15 KB)
Attached To
Mode
D16778: Begin navigating the mess that is edits to recurring events
Attached
Detach File
Event Timeline
Log In to Comment