Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15392204
D16782.id40416.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
D16782.id40416.diff
View Options
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
@@ -48,40 +48,8 @@
// are cancelling a child and all future events.
$must_fork = ($is_child && $is_future) ||
($is_parent && !$is_future);
-
if ($must_fork) {
- if ($is_child) {
- $fork_target = $event;
- } else {
- if ($event->isValidSequenceIndex($viewer, 1)) {
- $next_event = id(new PhabricatorCalendarEventQuery())
- ->setViewer($viewer)
- ->withInstanceSequencePairs(
- array(
- array($event->getPHID(), 1),
- ))
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->executeOne();
-
- if (!$next_event) {
- $next_event = $event->newStub($viewer, 1);
- }
-
- $fork_target = $next_event;
- } else {
- // This appears to be a "recurring" event with no valid
- // instances: for example, its "until" date is before the second
- // instance would occur. This can happen if we already forked the
- // event or if users entered silly stuff. Just edit the event
- // directly without forking anything.
- $fork_target = null;
- }
- }
-
+ $fork_target = $event->loadForkTarget($viewer);
if ($fork_target) {
$xactions = array();
@@ -101,26 +69,7 @@
}
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.
-
- // NOTE: This only affects events that are currently in the same
- // series, not all events that were ever in the original series.
- // We could use series PHIDs instead of parent PHIDs to affect more
- // events if this turns out to be counterintuitive. Other
- // applications differ in their behavior.
-
- $future = id(new PhabricatorCalendarEventQuery())
- ->setViewer($viewer)
- ->withParentEventPHIDs(array($event->getPHID()))
- ->withUTCInitialEpochBetween($event->getUTCInitialEpoch(), null)
- ->requireCapabilities(
- array(
- PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
- ))
- ->execute();
+ $future = $event->loadFutureEvents($viewer);
foreach ($future as $future_event) {
$targets[] = $future_event;
}
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
@@ -6,6 +6,9 @@
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
+ $engine = id(new PhabricatorCalendarEventEditEngine())
+ ->setController($this);
+
$id = $request->getURIData('id');
if ($id) {
$event = id(new PhabricatorCalendarEventQuery())
@@ -16,11 +19,57 @@
if ($response) {
return $response;
}
+
+ $cancel_uri = $event->getURI();
+
+ $page = $request->getURIData('pageKey');
+ if ($page == 'recurring') {
+ if ($event->isChildEvent()) {
+ return $this->newDialog()
+ ->setTitle(pht('Series Event'))
+ ->appendParagraph(
+ pht(
+ 'This event is an instance in an event series. To change '.
+ 'the behavior for the series, edit the parent event.'))
+ ->addCancelButton($cancel_uri);
+ }
+ } else if ($event->getIsRecurring()) {
+ $mode = $request->getStr('mode');
+ if (!$mode) {
+ $form = id(new AphrontFormView())
+ ->setViewer($viewer)
+ ->appendControl(
+ id(new AphrontFormSelectControl())
+ ->setLabel(pht('Edit Events'))
+ ->setName('mode')
+ ->setOptions(
+ array(
+ PhabricatorCalendarEventEditEngine::MODE_THIS
+ => pht('Edit Only This Event'),
+ PhabricatorCalendarEventEditEngine::MODE_FUTURE
+ => pht('Edit All Future Events'),
+ )));
+
+ return $this->newDialog()
+ ->setTitle(pht('Edit Event'))
+ ->appendParagraph(
+ pht(
+ 'This event is part of a series. Which events do you '.
+ 'want to edit?'))
+ ->appendForm($form)
+ ->addSubmitButton(pht('Continue'))
+ ->addCancelButton($cancel_uri)
+ ->setDisableWorkflowOnSubmit(true);
+
+ }
+
+ $engine
+ ->addContextParameter('mode', $mode)
+ ->setSeriesEditMode($mode);
+ }
}
- return id(new PhabricatorCalendarEventEditEngine())
- ->setController($this)
- ->buildResponse();
+ return $engine->buildResponse();
}
}
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
@@ -147,12 +147,8 @@
$edit_uri = "event/edit/{$id}/";
$edit_uri = $this->getApplicationURI($edit_uri);
-
- if ($event->isChildEvent()) {
- $edit_label = pht('Edit This Instance');
- } else {
- $edit_label = pht('Edit Event');
- }
+ $is_recurring = $event->getIsRecurring();
+ $edit_label = pht('Edit Event');
$curtain = $this->newCurtainView($event);
@@ -163,7 +159,7 @@
->setIcon('fa-pencil')
->setHref($edit_uri)
->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit));
+ ->setWorkflow(!$can_edit || $is_recurring));
}
$recurring_uri = "{$edit_uri}page/recurring/";
diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php
--- a/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php
+++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditEngine.php
@@ -5,6 +5,21 @@
const ENGINECONST = 'calendar.event';
+ private $rawTransactions;
+ private $seriesEditMode = self::MODE_THIS;
+
+ const MODE_THIS = 'this';
+ const MODE_FUTURE = 'future';
+
+ public function setSeriesEditMode($series_edit_mode) {
+ $this->seriesEditMode = $series_edit_mode;
+ return $this;
+ }
+
+ public function getSeriesEditMode() {
+ return $this->seriesEditMode;
+ }
+
public function getEngineName() {
return pht('Calendar Events');
}
@@ -77,6 +92,10 @@
$frequency = null;
}
+ // At least for now, just hide "Invitees" when editing all future events.
+ // This may eventually deserve a more nuanced approach.
+ $hide_invitees = ($this->getSeriesEditMode() == self::MODE_FUTURE);
+
$fields = array(
id(new PhabricatorTextEditField())
->setKey('name')
@@ -143,6 +162,7 @@
->setConduitTypeDescription(pht('New event host.'))
->setSingleValue($object->getHostPHID()),
id(new PhabricatorDatasourceEditField())
+ ->setIsHidden($hide_invitees)
->setKey('inviteePHIDs')
->setAliases(array('invite', 'invitee', 'invitees', 'inviteePHID'))
->setLabel(pht('Invitees'))
@@ -273,4 +293,77 @@
);
}
+ protected function willApplyTransactions($object, array $xactions) {
+ $viewer = $this->getViewer();
+ $this->rawTransactions = $xactions;
+
+ $is_parent = $object->isParentEvent();
+ $is_child = $object->isChildEvent();
+ $is_future = ($this->getSeriesEditMode() === self::MODE_FUTURE);
+
+ $must_fork = ($is_child && $is_future) ||
+ ($is_parent && !$is_future);
+
+ if ($must_fork) {
+ $fork_target = $object->loadForkTarget($viewer);
+ if ($fork_target) {
+ $fork_xaction = id(new PhabricatorCalendarEventTransaction())
+ ->setTransactionType(
+ PhabricatorCalendarEventForkTransaction::TRANSACTIONTYPE)
+ ->setNewValue(true);
+
+ if ($fork_target->getPHID() == $object->getPHID()) {
+ // We're forking the object itself, so just slip it into the
+ // transactions we're going to apply.
+ array_unshift($xactions, $fork_xaction);
+ } else {
+ // Otherwise, we're forking a different object, so we have to
+ // apply that separately.
+ $this->applyTransactions($fork_target, array($fork_xaction));
+ }
+ }
+ }
+
+ return $xactions;
+ }
+
+ protected function didApplyTransactions($object, array $xactions) {
+ $viewer = $this->getViewer();
+
+ if ($this->getSeriesEditMode() !== self::MODE_FUTURE) {
+ return;
+ }
+
+ $targets = $object->loadFutureEvents($viewer);
+ if (!$targets) {
+ return;
+ }
+
+ foreach ($targets as $target) {
+ $apply = clone $this->rawTransactions;
+ $this->applyTransactions($target, $apply);
+ }
+ }
+
+ private function applyTransactions($target, array $xactions) {
+ $viewer = $this->getViewer();
+
+ // TODO: This isn't the most accurate source we could use, but this mode
+ // is web-only for now.
+ $content_source = PhabricatorContentSource::newForSource(
+ PhabricatorWebContentSource::SOURCECONST);
+
+ $editor = id(new PhabricatorCalendarEventEditor())
+ ->setActor($viewer)
+ ->setContentSource($content_source)
+ ->setContinueOnNoEffect(true)
+ ->setContinueOnMissingFields(true);
+
+ try {
+ $editor->applyTransactions($target, $xactions);
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ // Just ignore any issues we run into.
+ }
+ }
+
}
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
@@ -154,6 +154,7 @@
->setSequenceIndex($sequence)
->setIsRecurring(true)
->attachParentEvent($this)
+ ->attachImportSource(null)
->setAllDayDateFrom(0)
->setAllDayDateTo(0)
->setDateFrom(0)
@@ -1060,6 +1061,69 @@
return $this;
}
+ public function loadForkTarget(PhabricatorUser $viewer) {
+ if (!$this->getIsRecurring()) {
+ // Can't fork an event which isn't recurring.
+ return null;
+ }
+
+ if ($this->isChildEvent()) {
+ // If this is a child event, this is the fork target.
+ return $this;
+ }
+
+ if (!$this->isValidSequenceIndex($viewer, 1)) {
+ // This appears to be a "recurring" event with no valid instances: for
+ // example, its "until" date is before the second instance would occur.
+ // This can happen if we already forked the event or if users entered
+ // silly stuff. Just edit the event directly without forking anything.
+ return null;
+ }
+
+
+ $next_event = id(new PhabricatorCalendarEventQuery())
+ ->setViewer($viewer)
+ ->withInstanceSequencePairs(
+ array(
+ array($this->getPHID(), 1),
+ ))
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
+
+ if (!$next_event) {
+ $next_event = $this->newStub($viewer, 1);
+ }
+
+ return $next_event;
+ }
+
+ public function loadFutureEvents(PhabricatorUser $viewer) {
+ // 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.
+
+ // NOTE: This only affects events that are currently in the same
+ // series, not all events that were ever in the original series.
+ // We could use series PHIDs instead of parent PHIDs to affect more
+ // events if this turns out to be counterintuitive. Other
+ // applications differ in their behavior.
+
+ return id(new PhabricatorCalendarEventQuery())
+ ->setViewer($viewer)
+ ->withParentEventPHIDs(array($this->getPHID()))
+ ->withUTCInitialEpochBetween($this->getUTCInitialEpoch(), null)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->execute();
+ }
+
/* -( Markup Interface )--------------------------------------------------- */
diff --git a/src/applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php
--- a/src/applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php
+++ b/src/applications/calendar/xaction/PhabricatorCalendarEventForkTransaction.php
@@ -33,7 +33,14 @@
$object->setSequenceIndex(0);
// Stop the parent event from recurring after the start date of this event.
- $parent->setUntilDateTime($object->newStartDateTime());
+ // Since the "until" time is inclusive, rewind it by one second. We could
+ // figure out the previous instance's time instead or use a COUNT, but this
+ // seems simpler as long as it doesn't cause any issues.
+ $until_cutoff = $object->newStartDateTime()
+ ->newRelativeDateTime('-PT1S')
+ ->newAbsoluteDateTime();
+
+ $parent->setUntilDateTime($until_cutoff);
$parent->save();
// NOTE: If we implement "COUNT" on editable events, we need to adjust
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php
--- a/src/applications/transactions/editengine/PhabricatorEditEngine.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php
@@ -996,9 +996,12 @@
->setContinueOnNoEffect(true);
try {
+ $xactions = $this->willApplyTransactions($object, $xactions);
$editor->applyTransactions($object, $xactions);
+ $this->didApplyTransactions($object, $xactions);
+
return $this->newEditResponse($request, $object, $xactions);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
@@ -2176,6 +2179,14 @@
return $page_map[$selected_key];
}
+ protected function willApplyTransactions($object, array $xactions) {
+ return $xactions;
+ }
+
+ protected function didApplyTransactions($object, array $xactions) {
+ return;
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Mar 16, 1:47 PM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7648326
Default Alt Text
D16782.id40416.diff (15 KB)
Attached To
Mode
D16782: When users edit recurring events, prompt to "Edit This Event" or "Edit All Future Events"
Attached
Detach File
Event Timeline
Log In to Comment