mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 11:30:55 +01:00
Generate "stub" events earlier, so more infrastructure works with Calendar
Summary: Ref T9275. When you create a recurring event which recurs forever, we want to avoid writing an infinite number of rows to the database. Currently, we write a row to the database right before you edit the event. Until then, we refer to it as `E123/999` or whatever ("instance 999 of event 123"). This creates a big mess with trying to make recurring events work with EditEngine, Subscriptions, Projects, Flags, Tokens, etc -- all of this stuff assumes that whatever you're working with has a PHID. I poked at letting this stuff work without a PHID a little bit, but that looked like a gigantic mess. Instead, generate an event "stub" a little sooner (when you look at the event detail page). This is basically just an ID/PHID to refer to the instance. Then, when you edit the stub, "materialize" it into a real event. This still has some issues, but I think it's more promising than the other approach was. Also: - Removes dead user profile calendar controller. - Replaces comments with EditEngine comments. Test Plan: - Commented on a recurring event. - Awarded tokens to a recurring event. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9275 Differential Revision: https://secure.phabricator.com/D16248
This commit is contained in:
parent
91a8a6d618
commit
3ab6a7e19f
17 changed files with 404 additions and 505 deletions
2
resources/sql/autopatches/20160707.calendar.01.stub.sql
Normal file
2
resources/sql/autopatches/20160707.calendar.01.stub.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
|
||||||
|
ADD isStub BOOL NOT NULL;
|
|
@ -2022,7 +2022,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorCalendarEditEngine' => 'applications/calendar/editor/PhabricatorCalendarEditEngine.php',
|
'PhabricatorCalendarEditEngine' => 'applications/calendar/editor/PhabricatorCalendarEditEngine.php',
|
||||||
'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php',
|
'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php',
|
||||||
'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php',
|
'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php',
|
||||||
'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php',
|
|
||||||
'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php',
|
'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php',
|
||||||
'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php',
|
'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php',
|
||||||
'PhabricatorCalendarEventEditProController' => 'applications/calendar/controller/PhabricatorCalendarEventEditProController.php',
|
'PhabricatorCalendarEventEditProController' => 'applications/calendar/controller/PhabricatorCalendarEventEditProController.php',
|
||||||
|
@ -2994,7 +2993,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
|
'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
|
||||||
'PhabricatorPeopleApplication' => 'applications/people/application/PhabricatorPeopleApplication.php',
|
'PhabricatorPeopleApplication' => 'applications/people/application/PhabricatorPeopleApplication.php',
|
||||||
'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
|
'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php',
|
||||||
'PhabricatorPeopleCalendarController' => 'applications/people/controller/PhabricatorPeopleCalendarController.php',
|
|
||||||
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
|
'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php',
|
||||||
'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php',
|
'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php',
|
||||||
'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php',
|
'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php',
|
||||||
|
@ -6633,7 +6631,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorFulltextInterface',
|
'PhabricatorFulltextInterface',
|
||||||
),
|
),
|
||||||
'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController',
|
'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController',
|
||||||
'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController',
|
|
||||||
'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController',
|
'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController',
|
||||||
'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController',
|
'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController',
|
||||||
'PhabricatorCalendarEventEditProController' => 'ManiphestController',
|
'PhabricatorCalendarEventEditProController' => 'ManiphestController',
|
||||||
|
@ -7741,7 +7738,6 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
|
'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
'PhabricatorPeopleApplication' => 'PhabricatorApplication',
|
'PhabricatorPeopleApplication' => 'PhabricatorApplication',
|
||||||
'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
|
'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController',
|
||||||
'PhabricatorPeopleCalendarController' => 'PhabricatorPeopleProfileController',
|
|
||||||
'PhabricatorPeopleController' => 'PhabricatorController',
|
'PhabricatorPeopleController' => 'PhabricatorController',
|
||||||
'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController',
|
'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController',
|
||||||
'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource',
|
'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
|
|
|
@ -40,7 +40,7 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
|
||||||
|
|
||||||
public function getRoutes() {
|
public function getRoutes() {
|
||||||
return array(
|
return array(
|
||||||
'/E(?P<id>[1-9]\d*)(?:/(?P<sequence>\d+))?'
|
'/E(?P<id>[1-9]\d*)(?:/(?P<sequence>\d+)/)?'
|
||||||
=> 'PhabricatorCalendarEventViewController',
|
=> 'PhabricatorCalendarEventViewController',
|
||||||
'/calendar/' => array(
|
'/calendar/' => array(
|
||||||
'(?:query/(?P<queryKey>[^/]+)/(?:(?P<year>\d+)/'.
|
'(?:query/(?P<queryKey>[^/]+)/(?:(?P<year>\d+)/'.
|
||||||
|
@ -51,15 +51,15 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
|
||||||
=> 'PhabricatorCalendarEventEditProController',
|
=> 'PhabricatorCalendarEventEditProController',
|
||||||
'create/'
|
'create/'
|
||||||
=> 'PhabricatorCalendarEventEditController',
|
=> 'PhabricatorCalendarEventEditController',
|
||||||
'edit/(?P<id>[1-9]\d*)/(?:(?P<sequence>\d+)/)?'
|
'edit/(?P<id>[1-9]\d*)/'
|
||||||
=> 'PhabricatorCalendarEventEditController',
|
=> 'PhabricatorCalendarEventEditController',
|
||||||
'drag/(?P<id>[1-9]\d*)/'
|
'drag/(?P<id>[1-9]\d*)/'
|
||||||
=> 'PhabricatorCalendarEventDragController',
|
=> 'PhabricatorCalendarEventDragController',
|
||||||
'cancel/(?P<id>[1-9]\d*)/(?:(?P<sequence>\d+)/)?'
|
'cancel/(?P<id>[1-9]\d*)/'
|
||||||
=> 'PhabricatorCalendarEventCancelController',
|
=> 'PhabricatorCalendarEventCancelController',
|
||||||
'(?P<action>join|decline|accept)/(?P<id>[1-9]\d*)/'
|
'(?P<action>join|decline|accept)/(?P<id>[1-9]\d*)/'
|
||||||
=> 'PhabricatorCalendarEventJoinController',
|
=> 'PhabricatorCalendarEventJoinController',
|
||||||
'comment/(?P<id>[1-9]\d*)/(?:(?P<sequence>\d+)/)?'
|
'comment/(?P<id>[1-9]\d*)/'
|
||||||
=> 'PhabricatorCalendarEventCommentController',
|
=> 'PhabricatorCalendarEventCommentController',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -30,49 +30,4 @@ abstract class PhabricatorCalendarController extends PhabricatorController {
|
||||||
return $crumbs;
|
return $crumbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getEventAtIndexForGhostPHID($viewer, $phid, $index) {
|
|
||||||
$result = id(new PhabricatorCalendarEventQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withInstanceSequencePairs(
|
|
||||||
array(
|
|
||||||
array(
|
|
||||||
$phid,
|
|
||||||
$index,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
->requireCapabilities(
|
|
||||||
array(
|
|
||||||
PhabricatorPolicyCapability::CAN_VIEW,
|
|
||||||
PhabricatorPolicyCapability::CAN_EDIT,
|
|
||||||
))
|
|
||||||
->executeOne();
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createEventFromGhost($viewer, $event, $index) {
|
|
||||||
$invitees = $event->getInvitees();
|
|
||||||
|
|
||||||
$new_ghost = $event->generateNthGhost($index, $viewer);
|
|
||||||
$new_ghost->attachParentEvent($event);
|
|
||||||
|
|
||||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
||||||
$new_ghost
|
|
||||||
->setID(null)
|
|
||||||
->setPHID(null)
|
|
||||||
->removeViewerTimezone($viewer)
|
|
||||||
->setViewPolicy($event->getViewPolicy())
|
|
||||||
->setEditPolicy($event->getEditPolicy())
|
|
||||||
->save();
|
|
||||||
$ghost_invitees = array();
|
|
||||||
foreach ($invitees as $invitee) {
|
|
||||||
$ghost_invitee = clone $invitee;
|
|
||||||
$ghost_invitee
|
|
||||||
->setID(null)
|
|
||||||
->setEventPHID($new_ghost->getPHID())
|
|
||||||
->save();
|
|
||||||
}
|
|
||||||
unset($unguarded);
|
|
||||||
return $new_ghost;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ final class PhabricatorCalendarEventCancelController
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $request->getViewer();
|
$viewer = $request->getViewer();
|
||||||
$id = $request->getURIData('id');
|
$id = $request->getURIData('id');
|
||||||
$sequence = $request->getURIData('sequence');
|
|
||||||
|
|
||||||
$event = id(new PhabricatorCalendarEventQuery())
|
$event = id(new PhabricatorCalendarEventQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
|
@ -17,40 +16,24 @@ final class PhabricatorCalendarEventCancelController
|
||||||
PhabricatorPolicyCapability::CAN_EDIT,
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
))
|
))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
|
|
||||||
if ($sequence) {
|
|
||||||
$parent_event = $event;
|
|
||||||
$event = $parent_event->generateNthGhost($sequence, $viewer);
|
|
||||||
$event->attachParentEvent($parent_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$event) {
|
if (!$event) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$sequence) {
|
$cancel_uri = $event->getURI();
|
||||||
$cancel_uri = '/E'.$event->getID();
|
|
||||||
|
$is_parent = $event->isParentEvent();
|
||||||
|
$is_child = $event->isChildEvent();
|
||||||
|
$is_cancelled = $event->getIsCancelled();
|
||||||
|
|
||||||
|
if ($is_child) {
|
||||||
|
$is_parent_cancelled = $event->getParentEvent()->getIsCancelled();
|
||||||
} else {
|
} else {
|
||||||
$cancel_uri = '/E'.$event->getID().'/'.$sequence;
|
$is_parent_cancelled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$is_cancelled = $event->getIsCancelled();
|
|
||||||
$is_parent_cancelled = $event->getIsParentCancelled();
|
|
||||||
$is_parent = $event->getIsRecurrenceParent();
|
|
||||||
|
|
||||||
$validation_exception = null;
|
$validation_exception = null;
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
if ($is_cancelled && $sequence) {
|
|
||||||
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
|
|
||||||
} else if ($sequence) {
|
|
||||||
$event = $this->createEventFromGhost(
|
|
||||||
$viewer,
|
|
||||||
$event,
|
|
||||||
$sequence);
|
|
||||||
$event->applyViewerTimezone($viewer);
|
|
||||||
}
|
|
||||||
|
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
|
|
||||||
$xaction = id(new PhabricatorCalendarEventTransaction())
|
$xaction = id(new PhabricatorCalendarEventTransaction())
|
||||||
|
@ -73,43 +56,47 @@ final class PhabricatorCalendarEventCancelController
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($is_cancelled) {
|
if ($is_cancelled) {
|
||||||
if ($sequence || $is_parent_cancelled) {
|
if ($is_parent_cancelled) {
|
||||||
$title = pht('Cannot Reinstate Instance');
|
$title = pht('Cannot Reinstate Instance');
|
||||||
$paragraph = pht(
|
$paragraph = pht(
|
||||||
'Cannot reinstate an instance of a cancelled recurring event.');
|
'You cannot reinstate an instance of a cancelled recurring event.');
|
||||||
$cancel = pht('Cancel');
|
$cancel = pht('Back');
|
||||||
$submit = null;
|
$submit = null;
|
||||||
} else if ($is_parent) {
|
} else if ($is_child) {
|
||||||
$title = pht('Reinstate Recurrence');
|
$title = pht('Reinstate Instance');
|
||||||
$paragraph = pht(
|
$paragraph = pht(
|
||||||
'Reinstate all instances of this recurrence
|
'Reinstate this instance of this recurring event?');
|
||||||
that have not been individually cancelled?');
|
$cancel = pht('Back');
|
||||||
$cancel = pht("Don't Reinstate Recurrence");
|
$submit = pht('Reinstate Instance');
|
||||||
$submit = pht('Reinstate Recurrence');
|
} 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');
|
||||||
} else {
|
} else {
|
||||||
$title = pht('Reinstate Event');
|
$title = pht('Reinstate Event');
|
||||||
$paragraph = pht('Reinstate this event?');
|
$paragraph = pht('Reinstate this event?');
|
||||||
$cancel = pht("Don't Reinstate Event");
|
$cancel = pht('Back');
|
||||||
$submit = pht('Reinstate Event');
|
$submit = pht('Reinstate Event');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($sequence) {
|
if ($is_child) {
|
||||||
$title = pht('Cancel Instance');
|
$title = pht('Cancel Instance');
|
||||||
$paragraph = pht(
|
$paragraph = pht('Cancel this instance of this recurring event?');
|
||||||
'Cancel just this instance of a recurring event.');
|
$cancel = pht('Back');
|
||||||
$cancel = pht("Don't Cancel Instance");
|
|
||||||
$submit = pht('Cancel Instance');
|
$submit = pht('Cancel Instance');
|
||||||
} else if ($is_parent) {
|
} else if ($is_parent) {
|
||||||
$title = pht('Cancel Recurrence');
|
$title = pht('Cancel Recurrin Event');
|
||||||
$paragraph = pht(
|
$paragraph = pht('Cancel this entire series of recurring events?');
|
||||||
'Cancel the entire series of recurring events?');
|
$cancel = pht('Back');
|
||||||
$cancel = pht("Don't Cancel Recurrence");
|
$submit = pht('Cancel Recurring Event');
|
||||||
$submit = pht('Cancel Recurrence');
|
|
||||||
} else {
|
} else {
|
||||||
$title = pht('Cancel Event');
|
$title = pht('Cancel Event');
|
||||||
$paragraph = pht(
|
$paragraph = pht(
|
||||||
'You can always reinstate the event later.');
|
'Cancel this event? You can always reinstate the event later.');
|
||||||
$cancel = pht("Don't Cancel Event");
|
$cancel = pht('Back');
|
||||||
$submit = pht('Cancel Event');
|
$submit = pht('Cancel Event');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
final class PhabricatorCalendarEventCommentController
|
|
||||||
extends PhabricatorCalendarController {
|
|
||||||
|
|
||||||
public function handleRequest(AphrontRequest $request) {
|
|
||||||
if (!$request->isFormPost()) {
|
|
||||||
return new Aphront400Response();
|
|
||||||
}
|
|
||||||
|
|
||||||
$viewer = $request->getViewer();
|
|
||||||
$id = $request->getURIData('id');
|
|
||||||
|
|
||||||
$is_preview = $request->isPreviewRequest();
|
|
||||||
$draft = PhabricatorDraft::buildFromRequest($request);
|
|
||||||
|
|
||||||
$event = id(new PhabricatorCalendarEventQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withIDs(array($id))
|
|
||||||
->executeOne();
|
|
||||||
if (!$event) {
|
|
||||||
return new Aphront404Response();
|
|
||||||
}
|
|
||||||
|
|
||||||
$index = $request->getURIData('sequence');
|
|
||||||
if ($index && !$is_preview) {
|
|
||||||
$result = $this->getEventAtIndexForGhostPHID(
|
|
||||||
$viewer,
|
|
||||||
$event->getPHID(),
|
|
||||||
$index);
|
|
||||||
|
|
||||||
if ($result) {
|
|
||||||
$event = $result;
|
|
||||||
} else {
|
|
||||||
$event = $this->createEventFromGhost(
|
|
||||||
$viewer,
|
|
||||||
$event,
|
|
||||||
$index);
|
|
||||||
$event->applyViewerTimezone($viewer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$view_uri = '/'.$event->getMonogram();
|
|
||||||
|
|
||||||
$xactions = array();
|
|
||||||
$xactions[] = id(new PhabricatorCalendarEventTransaction())
|
|
||||||
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
|
|
||||||
->attachComment(
|
|
||||||
id(new PhabricatorCalendarEventTransactionComment())
|
|
||||||
->setContent($request->getStr('comment')));
|
|
||||||
|
|
||||||
$editor = id(new PhabricatorCalendarEventEditor())
|
|
||||||
->setActor($viewer)
|
|
||||||
->setContinueOnNoEffect($request->isContinueRequest())
|
|
||||||
->setContentSourceFromRequest($request)
|
|
||||||
->setIsPreview($is_preview);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$xactions = $editor->applyTransactions($event, $xactions);
|
|
||||||
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
|
|
||||||
return id(new PhabricatorApplicationTransactionNoEffectResponse())
|
|
||||||
->setCancelURI($view_uri)
|
|
||||||
->setException($ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($draft) {
|
|
||||||
$draft->replaceOrDelete();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->isAjax() && $is_preview) {
|
|
||||||
return id(new PhabricatorApplicationTransactionResponse())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->setTransactions($xactions)
|
|
||||||
->setIsPreview($is_preview);
|
|
||||||
} else {
|
|
||||||
return id(new AphrontRedirectResponse())
|
|
||||||
->setURI($view_uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -77,41 +77,18 @@ final class PhabricatorCalendarEventEditController
|
||||||
$cancel_uri = $this->getApplicationURI();
|
$cancel_uri = $this->getApplicationURI();
|
||||||
} else {
|
} else {
|
||||||
$event = id(new PhabricatorCalendarEventQuery())
|
$event = id(new PhabricatorCalendarEventQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withIDs(array($this->id))
|
->withIDs(array($this->id))
|
||||||
->requireCapabilities(
|
->requireCapabilities(
|
||||||
array(
|
array(
|
||||||
PhabricatorPolicyCapability::CAN_VIEW,
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT,
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
))
|
))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
|
|
||||||
if (!$event) {
|
if (!$event) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->getURIData('sequence')) {
|
|
||||||
$index = $request->getURIData('sequence');
|
|
||||||
|
|
||||||
$result = $this->getEventAtIndexForGhostPHID(
|
|
||||||
$viewer,
|
|
||||||
$event->getPHID(),
|
|
||||||
$index);
|
|
||||||
|
|
||||||
if ($result) {
|
|
||||||
return id(new AphrontRedirectResponse())
|
|
||||||
->setURI('/calendar/event/edit/'.$result->getID().'/');
|
|
||||||
}
|
|
||||||
|
|
||||||
$event = $this->createEventFromGhost(
|
|
||||||
$viewer,
|
|
||||||
$event,
|
|
||||||
$index);
|
|
||||||
|
|
||||||
return id(new AphrontRedirectResponse())
|
|
||||||
->setURI('/calendar/event/edit/'.$event->getID().'/');
|
|
||||||
}
|
|
||||||
|
|
||||||
$end_value = AphrontFormDateControlValue::newFromEpoch(
|
$end_value = AphrontFormDateControlValue::newFromEpoch(
|
||||||
$viewer,
|
$viewer,
|
||||||
$event->getDateTo());
|
$event->getDateTo());
|
||||||
|
@ -137,7 +114,7 @@ final class PhabricatorCalendarEventEditController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$cancel_uri = '/'.$event->getMonogram();
|
$cancel_uri = $event->getURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->isCreate()) {
|
if ($this->isCreate()) {
|
||||||
|
@ -153,7 +130,7 @@ final class PhabricatorCalendarEventEditController
|
||||||
$description = $event->getDescription();
|
$description = $event->getDescription();
|
||||||
$is_all_day = $event->getIsAllDay();
|
$is_all_day = $event->getIsAllDay();
|
||||||
$is_recurring = $event->getIsRecurring();
|
$is_recurring = $event->getIsRecurring();
|
||||||
$is_parent = $event->getIsRecurrenceParent();
|
$is_parent = $event->isParentEvent();
|
||||||
$frequency = idx($event->getRecurrenceFrequency(), 'rule');
|
$frequency = idx($event->getRecurrenceFrequency(), 'rule');
|
||||||
$icon = $event->getIcon();
|
$icon = $event->getIcon();
|
||||||
$edit_policy = $event->getEditPolicy();
|
$edit_policy = $event->getEditPolicy();
|
||||||
|
|
|
@ -20,12 +20,11 @@ final class PhabricatorCalendarEventJoinController
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->withIDs(array($id))
|
->withIDs(array($id))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
|
|
||||||
if (!$event) {
|
if (!$event) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
$cancel_uri = '/E'.$event->getID();
|
$cancel_uri = $event->getURI();
|
||||||
$validation_exception = null;
|
$validation_exception = null;
|
||||||
|
|
||||||
$is_attending = $event->getIsUserAttending($viewer->getPHID());
|
$is_attending = $event->getIsUserAttending($viewer->getPHID());
|
||||||
|
|
|
@ -9,89 +9,48 @@ final class PhabricatorCalendarEventViewController
|
||||||
|
|
||||||
public function handleRequest(AphrontRequest $request) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$viewer = $request->getViewer();
|
$viewer = $request->getViewer();
|
||||||
$id = $request->getURIData('id');
|
|
||||||
$sequence = $request->getURIData('sequence');
|
|
||||||
|
|
||||||
$timeline = null;
|
$event = $this->loadEvent();
|
||||||
|
|
||||||
$event = id(new PhabricatorCalendarEventQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withIDs(array($id))
|
|
||||||
->executeOne();
|
|
||||||
if (!$event) {
|
if (!$event) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($sequence) {
|
// If we looked up or generated a stub event, redirect to that event's
|
||||||
$result = $this->getEventAtIndexForGhostPHID(
|
// canonical URI.
|
||||||
$viewer,
|
$id = $request->getURIData('id');
|
||||||
$event->getPHID(),
|
if ($event->getID() != $id) {
|
||||||
$sequence);
|
$uri = $event->getURI();
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||||
if ($result) {
|
|
||||||
$parent_event = $event;
|
|
||||||
$event = $result;
|
|
||||||
$event->attachParentEvent($parent_event);
|
|
||||||
return id(new AphrontRedirectResponse())
|
|
||||||
->setURI('/E'.$result->getID());
|
|
||||||
} else if ($sequence && $event->getIsRecurring()) {
|
|
||||||
$parent_event = $event;
|
|
||||||
$event = $event->generateNthGhost($sequence, $viewer);
|
|
||||||
$event->attachParentEvent($parent_event);
|
|
||||||
} else if ($sequence) {
|
|
||||||
return new Aphront404Response();
|
|
||||||
}
|
|
||||||
|
|
||||||
$title = $event->getMonogram().' ('.$sequence.')';
|
|
||||||
$page_title = $title.' '.$event->getName();
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
|
||||||
$crumbs->addTextCrumb($title, '/'.$event->getMonogram().'/'.$sequence);
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$title = 'E'.$event->getID();
|
|
||||||
$page_title = $title.' '.$event->getName();
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
|
||||||
$crumbs->addTextCrumb($title);
|
|
||||||
$crumbs->setBorder(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$event->getIsGhostEvent()) {
|
$monogram = $event->getMonogram();
|
||||||
$timeline = $this->buildTransactionTimeline(
|
$page_title = $monogram.' '.$event->getName();
|
||||||
$event,
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
new PhabricatorCalendarEventTransactionQuery());
|
$crumbs->addTextCrumb($monogram);
|
||||||
}
|
$crumbs->setBorder(true);
|
||||||
|
|
||||||
|
$timeline = $this->buildTransactionTimeline(
|
||||||
|
$event,
|
||||||
|
new PhabricatorCalendarEventTransactionQuery());
|
||||||
|
|
||||||
$header = $this->buildHeaderView($event);
|
$header = $this->buildHeaderView($event);
|
||||||
$curtain = $this->buildCurtain($event);
|
$curtain = $this->buildCurtain($event);
|
||||||
$details = $this->buildPropertySection($event);
|
$details = $this->buildPropertySection($event);
|
||||||
$description = $this->buildDescriptionView($event);
|
$description = $this->buildDescriptionView($event);
|
||||||
|
|
||||||
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
|
$comment_view = id(new PhabricatorCalendarEditEngine())
|
||||||
$add_comment_header = $is_serious
|
->setViewer($viewer)
|
||||||
? pht('Add Comment')
|
->buildEditEngineCommentView($event);
|
||||||
: pht('Add To Plate');
|
|
||||||
$draft = PhabricatorDraft::newFromUserAndKey($viewer, $event->getPHID());
|
$timeline->setQuoteRef($monogram);
|
||||||
if ($sequence) {
|
$comment_view->setTransactionTimeline($timeline);
|
||||||
$comment_uri = $this->getApplicationURI(
|
|
||||||
'/event/comment/'.$event->getID().'/'.$sequence.'/');
|
|
||||||
} else {
|
|
||||||
$comment_uri = $this->getApplicationURI(
|
|
||||||
'/event/comment/'.$event->getID().'/');
|
|
||||||
}
|
|
||||||
$add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
|
|
||||||
->setUser($viewer)
|
|
||||||
->setObjectPHID($event->getPHID())
|
|
||||||
->setDraft($draft)
|
|
||||||
->setHeaderText($add_comment_header)
|
|
||||||
->setAction($comment_uri)
|
|
||||||
->setSubmitButtonName(pht('Add Comment'));
|
|
||||||
|
|
||||||
$view = id(new PHUITwoColumnView())
|
$view = id(new PHUITwoColumnView())
|
||||||
->setHeader($header)
|
->setHeader($header)
|
||||||
->setMainColumn(array(
|
->setMainColumn(
|
||||||
|
array(
|
||||||
$timeline,
|
$timeline,
|
||||||
$add_comment_form,
|
$comment_view,
|
||||||
))
|
))
|
||||||
->setCurtain($curtain)
|
->setCurtain($curtain)
|
||||||
->addPropertySection(pht('Details'), $details)
|
->addPropertySection(pht('Details'), $details)
|
||||||
|
@ -101,10 +60,7 @@ final class PhabricatorCalendarEventViewController
|
||||||
->setTitle($page_title)
|
->setTitle($page_title)
|
||||||
->setCrumbs($crumbs)
|
->setCrumbs($crumbs)
|
||||||
->setPageObjectPHIDs(array($event->getPHID()))
|
->setPageObjectPHIDs(array($event->getPHID()))
|
||||||
->appendChild(
|
->appendChild($view);
|
||||||
array(
|
|
||||||
$view,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildHeaderView(
|
private function buildHeaderView(
|
||||||
|
@ -152,7 +108,7 @@ final class PhabricatorCalendarEventViewController
|
||||||
private function buildCurtain(PhabricatorCalendarEvent $event) {
|
private function buildCurtain(PhabricatorCalendarEvent $event) {
|
||||||
$viewer = $this->getRequest()->getUser();
|
$viewer = $this->getRequest()->getUser();
|
||||||
$id = $event->getID();
|
$id = $event->getID();
|
||||||
$is_cancelled = $event->getIsCancelled();
|
$is_cancelled = $event->isCancelledEvent();
|
||||||
$is_attending = $event->getIsUserAttending($viewer->getPHID());
|
$is_attending = $event->getIsUserAttending($viewer->getPHID());
|
||||||
|
|
||||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||||
|
@ -160,19 +116,11 @@ final class PhabricatorCalendarEventViewController
|
||||||
$event,
|
$event,
|
||||||
PhabricatorPolicyCapability::CAN_EDIT);
|
PhabricatorPolicyCapability::CAN_EDIT);
|
||||||
|
|
||||||
$edit_label = false;
|
$edit_uri = "event/edit/{$id}/";
|
||||||
$edit_uri = false;
|
if ($event->isChildEvent()) {
|
||||||
|
|
||||||
if ($event->getIsGhostEvent()) {
|
|
||||||
$index = $event->getSequenceIndex();
|
|
||||||
$edit_label = pht('Edit This Instance');
|
$edit_label = pht('Edit This Instance');
|
||||||
$edit_uri = "event/edit/{$id}/{$index}/";
|
|
||||||
} else if ($event->getIsRecurrenceException()) {
|
|
||||||
$edit_label = pht('Edit This Instance');
|
|
||||||
$edit_uri = "event/edit/{$id}/";
|
|
||||||
} else {
|
} else {
|
||||||
$edit_label = pht('Edit');
|
$edit_label = pht('Edit');
|
||||||
$edit_uri = "event/edit/{$id}/";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$curtain = $this->newCurtainView($event);
|
$curtain = $this->newCurtainView($event);
|
||||||
|
@ -204,28 +152,21 @@ final class PhabricatorCalendarEventViewController
|
||||||
}
|
}
|
||||||
|
|
||||||
$cancel_uri = $this->getApplicationURI("event/cancel/{$id}/");
|
$cancel_uri = $this->getApplicationURI("event/cancel/{$id}/");
|
||||||
|
$cancel_disabled = !$can_edit;
|
||||||
|
|
||||||
if ($event->getIsGhostEvent()) {
|
if ($event->isChildEvent()) {
|
||||||
$index = $event->getSequenceIndex();
|
|
||||||
$can_reinstate = $event->getIsParentCancelled();
|
|
||||||
|
|
||||||
$cancel_label = pht('Cancel This Instance');
|
$cancel_label = pht('Cancel This Instance');
|
||||||
$reinstate_label = pht('Reinstate This Instance');
|
$reinstate_label = pht('Reinstate This Instance');
|
||||||
$cancel_disabled = (!$can_edit || $can_reinstate);
|
|
||||||
$cancel_uri = $this->getApplicationURI("event/cancel/{$id}/{$index}/");
|
if ($event->getParentEvent()->getIsCancelled()) {
|
||||||
} else if ($event->getIsRecurrenceException()) {
|
$cancel_disabled = true;
|
||||||
$can_reinstate = $event->getIsParentCancelled();
|
}
|
||||||
$cancel_label = pht('Cancel This Instance');
|
} else if ($event->isParentEvent()) {
|
||||||
$reinstate_label = pht('Reinstate This Instance');
|
|
||||||
$cancel_disabled = (!$can_edit || $can_reinstate);
|
|
||||||
} else if ($event->getIsRecurrenceParent()) {
|
|
||||||
$cancel_label = pht('Cancel All');
|
$cancel_label = pht('Cancel All');
|
||||||
$reinstate_label = pht('Reinstate All');
|
$reinstate_label = pht('Reinstate All');
|
||||||
$cancel_disabled = !$can_edit;
|
|
||||||
} else {
|
} else {
|
||||||
$cancel_label = pht('Cancel Event');
|
$cancel_label = pht('Cancel Event');
|
||||||
$reinstate_label = pht('Reinstate Event');
|
$reinstate_label = pht('Reinstate Event');
|
||||||
$cancel_disabled = !$can_edit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($is_cancelled) {
|
if ($is_cancelled) {
|
||||||
|
@ -385,4 +326,68 @@ final class PhabricatorCalendarEventViewController
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function loadEvent() {
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$id = $request->getURIData('id');
|
||||||
|
$sequence = $request->getURIData('sequence');
|
||||||
|
|
||||||
|
// We're going to figure out which event you're trying to look at. Most of
|
||||||
|
// the time this is simple, but you may be looking at an instance of a
|
||||||
|
// recurring event which we haven't generated an object for.
|
||||||
|
|
||||||
|
// If you are, we're going to generate a "stub" event so we have a real
|
||||||
|
// ID and PHID to work with, since the rest of the infrastructure relies
|
||||||
|
// on these identifiers existing.
|
||||||
|
|
||||||
|
// Load the event identified by ID first.
|
||||||
|
$event = id(new PhabricatorCalendarEventQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($id))
|
||||||
|
->executeOne();
|
||||||
|
if (!$event) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we aren't looking at an instance of this event, this is a completely
|
||||||
|
// normal request and we can just return this event.
|
||||||
|
if (!$sequence) {
|
||||||
|
return $event;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When you view "E123/999", E123 is normally the parent event. However,
|
||||||
|
// you might visit a different instance first instead and then fiddle
|
||||||
|
// with the URI. If the event we're looking at is a child, we are going
|
||||||
|
// to act on the parent instead.
|
||||||
|
if ($event->isChildEvent()) {
|
||||||
|
$event = $event->getParentEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to load the instance. If it already exists, we're all done and
|
||||||
|
// can just return it.
|
||||||
|
$instance = id(new PhabricatorCalendarEventQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withInstanceSequencePairs(
|
||||||
|
array(
|
||||||
|
array($event->getPHID(), $sequence),
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if ($instance) {
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$viewer->isLoggedIn()) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'This event instance has not been created yet. Log in to create '.
|
||||||
|
'it.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance = $event->newStub($viewer, $sequence);
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,10 @@ final class PhabricatorCalendarEditEngine
|
||||||
return $object->getURI();
|
return $object->getURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getEditorURI() {
|
||||||
|
return $this->getApplication()->getApplicationURI('event/editpro/');
|
||||||
|
}
|
||||||
|
|
||||||
protected function buildCustomEditFields($object) {
|
protected function buildCustomEditFields($object) {
|
||||||
$fields = array(
|
$fields = array(
|
||||||
id(new PhabricatorTextEditField())
|
id(new PhabricatorTextEditField())
|
||||||
|
|
|
@ -11,6 +11,47 @@ final class PhabricatorCalendarEventEditor
|
||||||
return pht('Calendar');
|
return pht('Calendar');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function shouldApplyInitialEffects(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
array $xactions) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function applyInitialEffects(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
array $xactions) {
|
||||||
|
|
||||||
|
$actor = $this->requireActor();
|
||||||
|
$object->removeViewerTimezone($actor);
|
||||||
|
|
||||||
|
if ($object->getIsStub()) {
|
||||||
|
$this->materializeStub($object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function materializeStub(PhabricatorCalendarEvent $event) {
|
||||||
|
if (!$event->getIsStub()) {
|
||||||
|
throw new Exception(
|
||||||
|
pht('Can not materialize an event stub: this event is not a stub.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$actor = $this->getActor();
|
||||||
|
$event->copyFromParent($actor);
|
||||||
|
$event->setIsStub(0);
|
||||||
|
|
||||||
|
$invitees = $event->getParentEvent()->getInvitees();
|
||||||
|
foreach ($invitees as $invitee) {
|
||||||
|
$invitee = id(new PhabricatorCalendarEventInvitee())
|
||||||
|
->setEventPHID($event->getPHID())
|
||||||
|
->setInviteePHID($invitee->getInviteePHID())
|
||||||
|
->setInviterPHID($invitee->getInviterPHID())
|
||||||
|
->setStatus($invitee->getStatus())
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->save();
|
||||||
|
}
|
||||||
|
|
||||||
public function getTransactionTypes() {
|
public function getTransactionTypes() {
|
||||||
$types = parent::getTransactionTypes();
|
$types = parent::getTransactionTypes();
|
||||||
|
|
||||||
|
@ -196,15 +237,6 @@ final class PhabricatorCalendarEventEditor
|
||||||
return parent::applyCustomExternalTransaction($object, $xaction);
|
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function didApplyInternalEffects(
|
|
||||||
PhabricatorLiskDAO $object,
|
|
||||||
array $xactions) {
|
|
||||||
|
|
||||||
$object->removeViewerTimezone($this->requireActor());
|
|
||||||
|
|
||||||
return $xactions;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function applyFinalEffects(
|
protected function applyFinalEffects(
|
||||||
PhabricatorLiskDAO $object,
|
PhabricatorLiskDAO $object,
|
||||||
array $xactions) {
|
array $xactions) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ final class PhabricatorCalendarEventQuery
|
||||||
private $isCancelled;
|
private $isCancelled;
|
||||||
private $eventsWithNoParent;
|
private $eventsWithNoParent;
|
||||||
private $instanceSequencePairs;
|
private $instanceSequencePairs;
|
||||||
|
private $isStub;
|
||||||
|
|
||||||
private $generateGhosts = false;
|
private $generateGhosts = false;
|
||||||
|
|
||||||
|
@ -55,6 +56,11 @@ final class PhabricatorCalendarEventQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function withIsStub($is_stub) {
|
||||||
|
$this->isStub = $is_stub;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function withEventsWithNoParent($events_with_no_parent) {
|
public function withEventsWithNoParent($events_with_no_parent) {
|
||||||
$this->eventsWithNoParent = $events_with_no_parent;
|
$this->eventsWithNoParent = $events_with_no_parent;
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -183,7 +189,7 @@ final class PhabricatorCalendarEventQuery
|
||||||
$sequence_start = max(1, $sequence_start);
|
$sequence_start = max(1, $sequence_start);
|
||||||
|
|
||||||
for ($index = $sequence_start; $index < $sequence_end; $index++) {
|
for ($index = $sequence_start; $index < $sequence_end; $index++) {
|
||||||
$events[] = $event->generateNthGhost($index, $viewer);
|
$events[] = $event->newGhost($viewer, $index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: We're slicing results every time because this makes it cheaper
|
// NOTE: We're slicing results every time because this makes it cheaper
|
||||||
|
@ -201,40 +207,66 @@ final class PhabricatorCalendarEventQuery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$map = array();
|
// Now that we're done generating ghost events, we're going to remove any
|
||||||
$instance_sequence_pairs = array();
|
// ghosts that we have concrete events for (or which we can load the
|
||||||
|
// concrete events for). These concrete events are generated when users
|
||||||
|
// edit a ghost, and replace the ghost events.
|
||||||
|
|
||||||
foreach ($events as $key => $event) {
|
// First, generate a map of all concrete <parentPHID, sequence> events we
|
||||||
|
// already loaded. We don't need to load these again.
|
||||||
|
$have_pairs = array();
|
||||||
|
foreach ($events as $event) {
|
||||||
if ($event->getIsGhostEvent()) {
|
if ($event->getIsGhostEvent()) {
|
||||||
$index = $event->getSequenceIndex();
|
continue;
|
||||||
$instance_sequence_pairs[] = array($event->getPHID(), $index);
|
|
||||||
$map[$event->getPHID()][$index] = $key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$parent_phid = $event->getInstanceOfEventPHID();
|
||||||
|
$sequence = $event->getSequenceIndex();
|
||||||
|
|
||||||
|
$have_pairs[$parent_phid][$sequence] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($instance_sequence_pairs) > 0) {
|
// Now, generate a map of all <parentPHID, sequence> events we generated
|
||||||
$sub_query = id(new PhabricatorCalendarEventQuery())
|
// ghosts for. We need to try to load these if we don't already have them.
|
||||||
->setViewer($viewer)
|
$map = array();
|
||||||
->setParentQuery($this)
|
$parent_pairs = array();
|
||||||
->withInstanceSequencePairs($instance_sequence_pairs)
|
foreach ($events as $key => $event) {
|
||||||
->execute();
|
if (!$event->getIsGhostEvent()) {
|
||||||
|
continue;
|
||||||
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();
|
$parent_phid = $event->getInstanceOfEventPHID();
|
||||||
foreach ($events as $key => $event) {
|
$sequence = $event->getSequenceIndex();
|
||||||
if ($event->getIsGhostEvent()) {
|
|
||||||
continue;
|
// We already loaded the concrete version of this event, so we can just
|
||||||
}
|
// throw out the ghost and move on.
|
||||||
if (isset($id_map[$event->getID()])) {
|
if (isset($have_pairs[$parent_phid][$sequence])) {
|
||||||
unset($events[$key]);
|
unset($events[$key]);
|
||||||
} else {
|
continue;
|
||||||
$id_map[$event->getID()] = true;
|
}
|
||||||
}
|
|
||||||
|
// We didn't load the concrete version of this event, so we need to
|
||||||
|
// try to load it if it exists.
|
||||||
|
$parent_pairs[] = array($parent_phid, $sequence);
|
||||||
|
$map[$parent_phid][$sequence] = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($parent_pairs) {
|
||||||
|
$instances = id(new self())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setParentQuery($this)
|
||||||
|
->withInstanceSequencePairs($parent_pairs)
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
foreach ($instances as $instance) {
|
||||||
|
$parent_phid = $instance->getInstanceOfEventPHID();
|
||||||
|
$sequence = $instance->getSequenceIndex();
|
||||||
|
|
||||||
|
$indexes = idx($map, $parent_phid);
|
||||||
|
$key = idx($indexes, $sequence);
|
||||||
|
|
||||||
|
// Replace the ghost with the corresponding concrete event.
|
||||||
|
$events[$key] = $instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,6 +361,13 @@ final class PhabricatorCalendarEventQuery
|
||||||
implode(' OR ', $sql));
|
implode(' OR ', $sql));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->isStub !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'event.isStub = %d',
|
||||||
|
(int)$this->isStub);
|
||||||
|
}
|
||||||
|
|
||||||
return $where;
|
return $where;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,15 @@ final class PhabricatorCalendarEventSearchEngine
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query->setGenerateGhosts(true);
|
// Generate ghosts (and ignore stub events) if we aren't querying for
|
||||||
|
// specific events.
|
||||||
|
if (!$map['ids'] && !$map['phids']) {
|
||||||
|
$query
|
||||||
|
->withIsStub(false)
|
||||||
|
->setGenerateGhosts(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getQueryDateRange(
|
private function getQueryDateRange(
|
||||||
|
|
|
@ -22,6 +22,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
||||||
protected $isAllDay;
|
protected $isAllDay;
|
||||||
protected $icon;
|
protected $icon;
|
||||||
protected $mailKey;
|
protected $mailKey;
|
||||||
|
protected $isStub;
|
||||||
|
|
||||||
protected $isRecurring = 0;
|
protected $isRecurring = 0;
|
||||||
protected $recurrenceFrequency = array();
|
protected $recurrenceFrequency = array();
|
||||||
|
@ -71,6 +72,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
||||||
->setUserPHID($actor->getPHID())
|
->setUserPHID($actor->getPHID())
|
||||||
->setIsCancelled(0)
|
->setIsCancelled(0)
|
||||||
->setIsAllDay(0)
|
->setIsAllDay(0)
|
||||||
|
->setIsStub(0)
|
||||||
->setIsRecurring($is_recurring)
|
->setIsRecurring($is_recurring)
|
||||||
->setIcon(self::DEFAULT_ICON)
|
->setIcon(self::DEFAULT_ICON)
|
||||||
->setViewPolicy($view_policy)
|
->setViewPolicy($view_policy)
|
||||||
|
@ -80,6 +82,116 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
||||||
->applyViewerTimezone($actor);
|
->applyViewerTimezone($actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function newChild(PhabricatorUser $actor, $sequence) {
|
||||||
|
if (!$this->isParentEvent()) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Unable to generate a new child event for an event which is not '.
|
||||||
|
'a recurring parent event!'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$child = id(new self())
|
||||||
|
->setIsCancelled(0)
|
||||||
|
->setIsStub(0)
|
||||||
|
->setInstanceOfEventPHID($this->getPHID())
|
||||||
|
->setSequenceIndex($sequence)
|
||||||
|
->setIsRecurring(true)
|
||||||
|
->setRecurrenceFrequency($this->getRecurrenceFrequency())
|
||||||
|
->attachParentEvent($this);
|
||||||
|
|
||||||
|
return $child->copyFromParent($actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function readField($field) {
|
||||||
|
static $inherit = array(
|
||||||
|
'userPHID' => true,
|
||||||
|
'isAllDay' => true,
|
||||||
|
'icon' => true,
|
||||||
|
'spacePHID' => true,
|
||||||
|
'viewPolicy' => true,
|
||||||
|
'editPolicy' => true,
|
||||||
|
'name' => true,
|
||||||
|
'description' => true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Read these fields from the parent event instead of this event. For
|
||||||
|
// example, we want any changes to the parent event's name to
|
||||||
|
if (isset($inherit[$field])) {
|
||||||
|
if ($this->getIsStub()) {
|
||||||
|
// TODO: This should be unconditional, but the execution order of
|
||||||
|
// CalendarEventQuery and applyViewerTimezone() are currently odd.
|
||||||
|
if ($this->parentEvent !== self::ATTACHABLE) {
|
||||||
|
return $this->getParentEvent()->readField($field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::readField($field);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function copyFromParent(PhabricatorUser $actor) {
|
||||||
|
if (!$this->isChildEvent()) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Unable to copy from parent event: this is not a child event.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$parent = $this->getParentEvent();
|
||||||
|
|
||||||
|
$this
|
||||||
|
->setUserPHID($parent->getUserPHID())
|
||||||
|
->setIsAllDay($parent->getIsAllDay())
|
||||||
|
->setIcon($parent->getIcon())
|
||||||
|
->setSpacePHID($parent->getSpacePHID())
|
||||||
|
->setViewPolicy($parent->getViewPolicy())
|
||||||
|
->setEditPolicy($parent->getEditPolicy())
|
||||||
|
->setName($parent->getName())
|
||||||
|
->setDescription($parent->getDescription());
|
||||||
|
|
||||||
|
$frequency = $parent->getFrequencyUnit();
|
||||||
|
$modify_key = '+'.$this->getSequenceIndex().' '.$frequency;
|
||||||
|
|
||||||
|
$date = $parent->getDateFrom();
|
||||||
|
$date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor);
|
||||||
|
$date_time->modify($modify_key);
|
||||||
|
$date = $date_time->format('U');
|
||||||
|
|
||||||
|
$duration = $parent->getDateTo() - $parent->getDateFrom();
|
||||||
|
|
||||||
|
$this
|
||||||
|
->setDateFrom($date)
|
||||||
|
->setDateTo($date + $duration);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newStub(PhabricatorUser $actor, $sequence) {
|
||||||
|
$stub = $this->newChild($actor, $sequence);
|
||||||
|
|
||||||
|
$stub->setIsStub(1);
|
||||||
|
|
||||||
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
$stub->save();
|
||||||
|
unset($unguarded);
|
||||||
|
|
||||||
|
$stub->applyViewerTimezone($actor);
|
||||||
|
|
||||||
|
return $stub;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newGhost(PhabricatorUser $actor, $sequence) {
|
||||||
|
$ghost = $this->newChild($actor, $sequence);
|
||||||
|
|
||||||
|
$ghost
|
||||||
|
->setIsGhostEvent(true)
|
||||||
|
->makeEphemeral();
|
||||||
|
|
||||||
|
$ghost->applyViewerTimezone($actor);
|
||||||
|
|
||||||
|
return $ghost;
|
||||||
|
}
|
||||||
|
|
||||||
public function applyViewerTimezone(PhabricatorUser $viewer) {
|
public function applyViewerTimezone(PhabricatorUser $viewer) {
|
||||||
if ($this->appliedViewer) {
|
if ($this->appliedViewer) {
|
||||||
throw new Exception(pht('Viewer timezone is already applied!'));
|
throw new Exception(pht('Viewer timezone is already applied!'));
|
||||||
|
@ -211,6 +323,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
||||||
'recurrenceEndDate' => 'epoch?',
|
'recurrenceEndDate' => 'epoch?',
|
||||||
'instanceOfEventPHID' => 'phid?',
|
'instanceOfEventPHID' => 'phid?',
|
||||||
'sequenceIndex' => 'uint32?',
|
'sequenceIndex' => 'uint32?',
|
||||||
|
'isStub' => 'bool',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'userPHID_dateFrom' => array(
|
'userPHID_dateFrom' => array(
|
||||||
|
@ -285,38 +398,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateNthGhost(
|
|
||||||
$sequence_index,
|
|
||||||
PhabricatorUser $actor) {
|
|
||||||
|
|
||||||
$frequency = $this->getFrequencyUnit();
|
|
||||||
$modify_key = '+'.$sequence_index.' '.$frequency;
|
|
||||||
|
|
||||||
$instance_of = ($this->getPHID()) ?
|
|
||||||
$this->getPHID() : $this->instanceOfEventPHID;
|
|
||||||
|
|
||||||
$date = $this->dateFrom;
|
|
||||||
$date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor);
|
|
||||||
$date_time->modify($modify_key);
|
|
||||||
$date = $date_time->format('U');
|
|
||||||
|
|
||||||
$duration = $this->dateTo - $this->dateFrom;
|
|
||||||
|
|
||||||
$edit_policy = PhabricatorPolicies::POLICY_NOONE;
|
|
||||||
|
|
||||||
$ghost_event = id(clone $this)
|
|
||||||
->setIsGhostEvent(true)
|
|
||||||
->setDateFrom($date)
|
|
||||||
->setDateTo($date + $duration)
|
|
||||||
->setIsRecurring(true)
|
|
||||||
->setRecurrenceFrequency($this->recurrenceFrequency)
|
|
||||||
->setInstanceOfEventPHID($instance_of)
|
|
||||||
->setSequenceIndex($sequence_index)
|
|
||||||
->setEditPolicy($edit_policy);
|
|
||||||
|
|
||||||
return $ghost_event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFrequencyUnit() {
|
public function getFrequencyUnit() {
|
||||||
$frequency = idx($this->recurrenceFrequency, 'rule');
|
$frequency = idx($this->recurrenceFrequency, 'rule');
|
||||||
|
|
||||||
|
@ -335,11 +416,13 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getURI() {
|
public function getURI() {
|
||||||
$uri = '/'.$this->getMonogram();
|
if ($this->getIsGhostEvent()) {
|
||||||
if ($this->isGhostEvent) {
|
$base = $this->getParentEvent()->getURI();
|
||||||
$uri = $uri.'/'.$this->sequenceIndex;
|
$sequence = $this->getSequenceIndex();
|
||||||
|
return "{$base}/{$sequence}/";
|
||||||
}
|
}
|
||||||
return $uri;
|
|
||||||
|
return '/'.$this->getMonogram();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getParentEvent() {
|
public function getParentEvent() {
|
||||||
|
@ -351,37 +434,25 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIsCancelled() {
|
public function isParentEvent() {
|
||||||
$instance_of = $this->instanceOfEventPHID;
|
return ($this->isRecurring && !$this->instanceOfEventPHID);
|
||||||
if ($instance_of != null && $this->getIsParentCancelled()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return $this->isCancelled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIsRecurrenceParent() {
|
public function isChildEvent() {
|
||||||
if ($this->isRecurring && !$this->instanceOfEventPHID) {
|
return ($this->instanceOfEventPHID !== null);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIsRecurrenceException() {
|
public function isCancelledEvent() {
|
||||||
if ($this->instanceOfEventPHID && !$this->isGhostEvent) {
|
if ($this->getIsCancelled()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIsParentCancelled() {
|
if ($this->isChildEvent()) {
|
||||||
if ($this->instanceOfEventPHID == null) {
|
if ($this->getParentEvent()->getIsCancelled()) {
|
||||||
return false;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$recurring_event = $this->getParentEvent();
|
|
||||||
if ($recurring_event->getIsCancelled()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,6 +479,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( Markup Interface )--------------------------------------------------- */
|
/* -( Markup Interface )--------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -314,6 +314,8 @@ final class ConpherenceThreadQuery
|
||||||
|
|
||||||
$events = array();
|
$events = array();
|
||||||
if ($participant_phids) {
|
if ($participant_phids) {
|
||||||
|
// TODO: All of this Calendar code is probably extra-broken, but none
|
||||||
|
// of it is currently reachable in the UI.
|
||||||
$events = id(new PhabricatorCalendarEventQuery())
|
$events = id(new PhabricatorCalendarEventQuery())
|
||||||
->setViewer($this->getViewer())
|
->setViewer($this->getViewer())
|
||||||
->withInvitedPHIDs($participant_phids)
|
->withInvitedPHIDs($participant_phids)
|
||||||
|
|
|
@ -69,7 +69,6 @@ final class PhabricatorPeopleApplication extends PhabricatorApplication {
|
||||||
'' => 'PhabricatorPeopleProfileViewController',
|
'' => 'PhabricatorPeopleProfileViewController',
|
||||||
'panel/'
|
'panel/'
|
||||||
=> $this->getPanelRouting('PhabricatorPeopleProfilePanelController'),
|
=> $this->getPanelRouting('PhabricatorPeopleProfilePanelController'),
|
||||||
'calendar/' => 'PhabricatorPeopleCalendarController',
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
final class PhabricatorPeopleCalendarController
|
|
||||||
extends PhabricatorPeopleProfileController {
|
|
||||||
|
|
||||||
public function shouldAllowPublic() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handleRequest(AphrontRequest $request) {
|
|
||||||
$viewer = $this->getViewer();
|
|
||||||
$username = $request->getURIData('username');
|
|
||||||
|
|
||||||
$user = id(new PhabricatorPeopleQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withUsernames(array($username))
|
|
||||||
->needProfileImage(true)
|
|
||||||
->executeOne();
|
|
||||||
if (!$user) {
|
|
||||||
return new Aphront404Response();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setUser($user);
|
|
||||||
|
|
||||||
$picture = $user->getProfileImageURI();
|
|
||||||
|
|
||||||
$now = time();
|
|
||||||
$request = $this->getRequest();
|
|
||||||
$year_d = phabricator_format_local_time($now, $user, 'Y');
|
|
||||||
$year = $request->getInt('year', $year_d);
|
|
||||||
$month_d = phabricator_format_local_time($now, $user, 'm');
|
|
||||||
$month = $request->getInt('month', $month_d);
|
|
||||||
$day = phabricator_format_local_time($now, $user, 'j');
|
|
||||||
|
|
||||||
$start_epoch = strtotime("{$year}-{$month}-01");
|
|
||||||
$end_epoch = strtotime("{$year}-{$month}-01 next month");
|
|
||||||
|
|
||||||
$statuses = id(new PhabricatorCalendarEventQuery())
|
|
||||||
->setViewer($user)
|
|
||||||
->withInvitedPHIDs(array($user->getPHID()))
|
|
||||||
->withDateRange(
|
|
||||||
$start_epoch,
|
|
||||||
$end_epoch)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$start_range_value = AphrontFormDateControlValue::newFromEpoch(
|
|
||||||
$user,
|
|
||||||
$start_epoch);
|
|
||||||
$end_range_value = AphrontFormDateControlValue::newFromEpoch(
|
|
||||||
$user,
|
|
||||||
$end_epoch);
|
|
||||||
|
|
||||||
if ($month == $month_d && $year == $year_d) {
|
|
||||||
$month_view = new PHUICalendarMonthView(
|
|
||||||
$start_range_value,
|
|
||||||
$end_range_value,
|
|
||||||
$month,
|
|
||||||
$year,
|
|
||||||
$day);
|
|
||||||
} else {
|
|
||||||
$month_view = new PHUICalendarMonthView(
|
|
||||||
$start_range_value,
|
|
||||||
$end_range_value,
|
|
||||||
$month,
|
|
||||||
$year);
|
|
||||||
}
|
|
||||||
|
|
||||||
$month_view->setBrowseURI($request->getRequestURI());
|
|
||||||
$month_view->setUser($user);
|
|
||||||
$month_view->setImage($picture);
|
|
||||||
|
|
||||||
$phids = mpull($statuses, 'getUserPHID');
|
|
||||||
$handles = $this->loadViewerHandles($phids);
|
|
||||||
|
|
||||||
foreach ($statuses as $status) {
|
|
||||||
$event = new AphrontCalendarEventView();
|
|
||||||
$event->setEpochRange($status->getDateFrom(), $status->getDateTo());
|
|
||||||
$event->setUserPHID($status->getUserPHID());
|
|
||||||
$event->setName($status->getName());
|
|
||||||
$event->setDescription($status->getDescription());
|
|
||||||
$event->setEventID($status->getID());
|
|
||||||
$month_view->addEvent($event);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nav = $this->getProfileMenu();
|
|
||||||
$nav->selectFilter('calendar');
|
|
||||||
|
|
||||||
$crumbs = $this->buildApplicationCrumbs();
|
|
||||||
$crumbs->addTextCrumb(pht('Calendar'));
|
|
||||||
|
|
||||||
return $this->newPage()
|
|
||||||
->setTitle(pht('Calendar'))
|
|
||||||
->setNavigation($nav)
|
|
||||||
->setCrumbs($crumbs)
|
|
||||||
->appendChild($month_view);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue