2014-02-06 10:07:42 -08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class PhabricatorCalendarEventQuery
|
|
|
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
|
|
|
|
|
|
|
private $ids;
|
2014-02-06 10:10:43 -08:00
|
|
|
private $phids;
|
2014-02-06 10:07:42 -08:00
|
|
|
private $rangeBegin;
|
|
|
|
private $rangeEnd;
|
2015-05-06 11:12:24 -07:00
|
|
|
private $inviteePHIDs;
|
2016-07-14 12:54:36 -07:00
|
|
|
private $hostPHIDs;
|
Canceling calendar events should deactivate the event
Summary: Closes T7943, Canceling calendar event should deactivate the event instead of destroying data.
Test Plan: Create an event, cancel it, see changed status icon, query for active events, event should not appear, query for deactivated events, event should appear in results.
Reviewers: #blessed_reviewers, epriestley
Reviewed By: #blessed_reviewers, epriestley
Subscribers: Korvin, epriestley
Maniphest Tasks: T7943
Differential Revision: https://secure.phabricator.com/D12604
2015-04-29 08:39:39 -07:00
|
|
|
private $isCancelled;
|
2015-06-03 09:27:39 -07:00
|
|
|
private $eventsWithNoParent;
|
2015-05-31 15:04:48 -07:00
|
|
|
private $instanceSequencePairs;
|
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
2016-07-07 07:19:58 -07:00
|
|
|
private $isStub;
|
2016-10-06 12:31:57 -07:00
|
|
|
private $parentEventPHIDs;
|
2016-10-11 11:45:57 -07:00
|
|
|
private $importSourcePHIDs;
|
2016-10-13 08:01:08 -07:00
|
|
|
private $importAuthorPHIDs;
|
|
|
|
private $importUIDs;
|
Begin navigating the mess that is edits to recurring events
Summary:
Ref T11804. This puts us on a path toward some kind of reasonable behavior here.
Currently, cancelling recurring events makes approximately zero sense ever in any situation.
Instead, give users the choice to cancel just the instance, or all future events. This is similar to Calendar.app. (Google Calendar has a third option, "All Events", which I may implement).
When the user picks something, basically do that.
The particulars of "do that" are messy. We have to split the series into two different series, stop the first series early, then edit the second series. Then we need to update any concrete events that are now part of the second series.
This code will get less junk in the next couple of diffs (I hope?) since I need to make it apply to edits, too, but this was a little easier to get started with.
Test Plan:
Cancelled an instance of an event; cancelled "All future events".
Both of them more or less worked in a reasonble way.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11804
Differential Revision: https://secure.phabricator.com/D16778
2016-10-31 12:47:39 -07:00
|
|
|
private $utcInitialEpochMin;
|
|
|
|
private $utcInitialEpochMax;
|
2016-10-31 18:01:33 -07:00
|
|
|
private $isImported;
|
Improve Calendar event behavior for group invites
Summary:
Ref T11816. Projects can be invited to an event, but the UI is currently fairly agnostic about them.
Instead, introduce the idea of "RSVPs", which are basically invites for you as an individual or for any group you're a part of. When we go to check if you're invited, we check for you individually first, then check for any groups you belong to if you haven't already accepted/declined.
On the calendar detail page:
- Show the quick "Join" / "Decline" buttons if any project you're a member of is invited.
- If you're invited, highlight any projects which you're a member of to make that more clear.
On other calendar views:
- If you're invited as part of a project, show the "multiple users" icon.
- If it's just you, continue showing the "add one user" icon.
Test Plan: Viewed month view, day view, detail view. Invited groups and individuals. Invited "Dog Project", accepted invite as user "Dog".
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11816
Differential Revision: https://secure.phabricator.com/D16868
2016-11-15 11:01:05 -08:00
|
|
|
private $needRSVPs;
|
2015-05-31 15:04:48 -07:00
|
|
|
|
2015-05-28 17:27:25 -07:00
|
|
|
private $generateGhosts = false;
|
|
|
|
|
2015-06-22 13:27:37 -07:00
|
|
|
public function newResultObject() {
|
|
|
|
return new PhabricatorCalendarEvent();
|
|
|
|
}
|
|
|
|
|
2015-05-28 17:27:25 -07:00
|
|
|
public function setGenerateGhosts($generate_ghosts) {
|
|
|
|
$this->generateGhosts = $generate_ghosts;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-02-06 10:07:42 -08:00
|
|
|
public function withIDs(array $ids) {
|
|
|
|
$this->ids = $ids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-02-06 10:10:43 -08:00
|
|
|
public function withPHIDs(array $phids) {
|
|
|
|
$this->phids = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-02-06 10:07:42 -08:00
|
|
|
public function withDateRange($begin, $end) {
|
|
|
|
$this->rangeBegin = $begin;
|
|
|
|
$this->rangeEnd = $end;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
Begin navigating the mess that is edits to recurring events
Summary:
Ref T11804. This puts us on a path toward some kind of reasonable behavior here.
Currently, cancelling recurring events makes approximately zero sense ever in any situation.
Instead, give users the choice to cancel just the instance, or all future events. This is similar to Calendar.app. (Google Calendar has a third option, "All Events", which I may implement).
When the user picks something, basically do that.
The particulars of "do that" are messy. We have to split the series into two different series, stop the first series early, then edit the second series. Then we need to update any concrete events that are now part of the second series.
This code will get less junk in the next couple of diffs (I hope?) since I need to make it apply to edits, too, but this was a little easier to get started with.
Test Plan:
Cancelled an instance of an event; cancelled "All future events".
Both of them more or less worked in a reasonble way.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11804
Differential Revision: https://secure.phabricator.com/D16778
2016-10-31 12:47:39 -07:00
|
|
|
public function withUTCInitialEpochBetween($min, $max) {
|
|
|
|
$this->utcInitialEpochMin = $min;
|
|
|
|
$this->utcInitialEpochMax = $max;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2014-02-06 10:07:42 -08:00
|
|
|
public function withInvitedPHIDs(array $phids) {
|
2015-05-06 11:12:24 -07:00
|
|
|
$this->inviteePHIDs = $phids;
|
2014-02-06 10:07:42 -08:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-07-14 12:54:36 -07:00
|
|
|
public function withHostPHIDs(array $phids) {
|
|
|
|
$this->hostPHIDs = $phids;
|
2014-02-06 10:10:18 -08:00
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
Canceling calendar events should deactivate the event
Summary: Closes T7943, Canceling calendar event should deactivate the event instead of destroying data.
Test Plan: Create an event, cancel it, see changed status icon, query for active events, event should not appear, query for deactivated events, event should appear in results.
Reviewers: #blessed_reviewers, epriestley
Reviewed By: #blessed_reviewers, epriestley
Subscribers: Korvin, epriestley
Maniphest Tasks: T7943
Differential Revision: https://secure.phabricator.com/D12604
2015-04-29 08:39:39 -07:00
|
|
|
public function withIsCancelled($is_cancelled) {
|
|
|
|
$this->isCancelled = $is_cancelled;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
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
2016-07-07 07:19:58 -07:00
|
|
|
public function withIsStub($is_stub) {
|
|
|
|
$this->isStub = $is_stub;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-06-03 09:27:39 -07:00
|
|
|
public function withEventsWithNoParent($events_with_no_parent) {
|
|
|
|
$this->eventsWithNoParent = $events_with_no_parent;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-05-31 15:04:48 -07:00
|
|
|
public function withInstanceSequencePairs(array $pairs) {
|
|
|
|
$this->instanceSequencePairs = $pairs;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-10-06 12:31:57 -07:00
|
|
|
public function withParentEventPHIDs(array $parent_phids) {
|
|
|
|
$this->parentEventPHIDs = $parent_phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-10-11 11:45:57 -07:00
|
|
|
public function withImportSourcePHIDs(array $import_phids) {
|
|
|
|
$this->importSourcePHIDs = $import_phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-10-13 08:01:08 -07:00
|
|
|
public function withImportAuthorPHIDs(array $author_phids) {
|
|
|
|
$this->importAuthorPHIDs = $author_phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function withImportUIDs(array $uids) {
|
|
|
|
$this->importUIDs = $uids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-10-31 18:01:33 -07:00
|
|
|
public function withIsImported($is_imported) {
|
|
|
|
$this->isImported = $is_imported;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
Improve Calendar event behavior for group invites
Summary:
Ref T11816. Projects can be invited to an event, but the UI is currently fairly agnostic about them.
Instead, introduce the idea of "RSVPs", which are basically invites for you as an individual or for any group you're a part of. When we go to check if you're invited, we check for you individually first, then check for any groups you belong to if you haven't already accepted/declined.
On the calendar detail page:
- Show the quick "Join" / "Decline" buttons if any project you're a member of is invited.
- If you're invited, highlight any projects which you're a member of to make that more clear.
On other calendar views:
- If you're invited as part of a project, show the "multiple users" icon.
- If it's just you, continue showing the "add one user" icon.
Test Plan: Viewed month view, day view, detail view. Invited groups and individuals. Invited "Dog Project", accepted invite as user "Dog".
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11816
Differential Revision: https://secure.phabricator.com/D16868
2016-11-15 11:01:05 -08:00
|
|
|
public function needRSVPs(array $phids) {
|
|
|
|
$this->needRSVPs = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2015-05-21 17:10:27 -07:00
|
|
|
protected function getDefaultOrderVector() {
|
|
|
|
return array('start', 'id');
|
|
|
|
}
|
|
|
|
|
2016-07-13 09:29:02 -07:00
|
|
|
public function getBuiltinOrders() {
|
|
|
|
return array(
|
|
|
|
'start' => array(
|
|
|
|
'vector' => array('start', 'id'),
|
|
|
|
'name' => pht('Event Start'),
|
|
|
|
),
|
|
|
|
) + parent::getBuiltinOrders();
|
|
|
|
}
|
|
|
|
|
2015-05-21 17:10:27 -07:00
|
|
|
public function getOrderableColumns() {
|
|
|
|
return array(
|
|
|
|
'start' => array(
|
|
|
|
'table' => $this->getPrimaryTableAlias(),
|
2016-11-01 13:57:18 -07:00
|
|
|
'column' => 'utcInitialEpoch',
|
2015-05-21 17:10:27 -07:00
|
|
|
'reverse' => true,
|
|
|
|
'type' => 'int',
|
|
|
|
'unique' => false,
|
|
|
|
),
|
|
|
|
) + parent::getOrderableColumns();
|
|
|
|
}
|
|
|
|
|
2019-03-18 11:44:54 -07:00
|
|
|
protected function newPagingMapFromPartialObject($object) {
|
2015-05-21 17:10:27 -07:00
|
|
|
return array(
|
2019-03-18 11:44:54 -07:00
|
|
|
'id' => (int)$object->getID(),
|
|
|
|
'start' => (int)$object->getStartDateTimeEpoch(),
|
2015-05-21 17:10:27 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-07-13 09:29:02 -07:00
|
|
|
protected function shouldLimitResults() {
|
|
|
|
// When generating ghosts, we can't rely on database ordering because
|
|
|
|
// MySQL can't predict the ghost start times. We'll just load all matching
|
|
|
|
// events, then generate results from there.
|
|
|
|
if ($this->generateGhosts) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-02-06 10:07:42 -08:00
|
|
|
protected function loadPage() {
|
2016-04-07 05:10:55 -07:00
|
|
|
$events = $this->loadStandardPage($this->newResultObject());
|
2015-05-07 18:57:28 -07:00
|
|
|
|
2016-04-07 05:10:55 -07:00
|
|
|
$viewer = $this->getViewer();
|
2015-05-07 18:57:28 -07:00
|
|
|
foreach ($events as $event) {
|
2016-04-07 05:10:55 -07:00
|
|
|
$event->applyViewerTimezone($viewer);
|
2015-05-07 18:57:28 -07:00
|
|
|
}
|
|
|
|
|
2015-05-28 17:27:25 -07:00
|
|
|
if (!$this->generateGhosts) {
|
|
|
|
return $events;
|
|
|
|
}
|
|
|
|
|
2016-04-07 05:10:55 -07:00
|
|
|
$raw_limit = $this->getRawResultLimit();
|
|
|
|
if (!$raw_limit && !$this->rangeEnd) {
|
|
|
|
throw new Exception(
|
|
|
|
pht(
|
|
|
|
'Event queries which generate ghost events must include either a '.
|
|
|
|
'result limit or an end date, because they may otherwise generate '.
|
|
|
|
'an infinite number of results. This query has neither.'));
|
|
|
|
}
|
2015-05-31 15:04:48 -07:00
|
|
|
|
2015-06-02 19:44:31 -07:00
|
|
|
foreach ($events as $key => $event) {
|
2015-05-28 17:27:25 -07:00
|
|
|
$sequence_start = 0;
|
2015-06-01 18:56:11 -07:00
|
|
|
$sequence_end = null;
|
|
|
|
$end = null;
|
2015-05-28 17:27:25 -07:00
|
|
|
|
2015-06-02 19:44:31 -07:00
|
|
|
$instance_of = $event->getInstanceOfEventPHID();
|
|
|
|
|
|
|
|
if ($instance_of == null && $this->isCancelled !== null) {
|
|
|
|
if ($event->getIsCancelled() != $this->isCancelled) {
|
|
|
|
unset($events[$key]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2016-07-13 09:29:02 -07:00
|
|
|
}
|
2015-06-02 19:44:31 -07:00
|
|
|
|
2016-07-13 09:29:02 -07:00
|
|
|
// Pull out all of the parents first. We may discard them as we begin
|
|
|
|
// generating ghost events, but we still want to process all of them.
|
|
|
|
$parents = array();
|
|
|
|
foreach ($events as $key => $event) {
|
|
|
|
if ($event->isParentEvent()) {
|
|
|
|
$parents[$key] = $event;
|
|
|
|
}
|
|
|
|
}
|
2015-05-28 17:27:25 -07:00
|
|
|
|
2016-07-13 09:29:02 -07:00
|
|
|
// Now that we've picked out all the parent events, we can immediately
|
|
|
|
// discard anything outside of the time window.
|
|
|
|
$events = $this->getEventsInRange($events);
|
2015-05-28 17:27:25 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
$generate_from = $this->rangeBegin;
|
|
|
|
$generate_until = $this->rangeEnd;
|
2016-07-13 09:29:02 -07:00
|
|
|
foreach ($parents as $key => $event) {
|
|
|
|
$duration = $event->getDuration();
|
2015-05-28 17:27:25 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
$start_date = $this->getRecurrenceWindowStart(
|
|
|
|
$event,
|
|
|
|
$generate_from - $duration);
|
2015-05-28 17:27:25 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
$end_date = $this->getRecurrenceWindowEnd(
|
|
|
|
$event,
|
|
|
|
$generate_until);
|
2015-05-28 17:27:25 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
$limit = $this->getRecurrenceLimit($event, $raw_limit);
|
2015-05-28 17:27:25 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
$set = $event->newRecurrenceSet();
|
2015-05-28 17:27:25 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
$recurrences = $set->getEventsBetween(
|
Fix an issue where recurring ghost events could go missing if queried with a limit
Summary:
Ref T11816. Depends on D17644. When you executed a query like "upcoming, limit 5 events" you might match some recurring events starting from, say, a year ago and repeating every month.
We'd then generate the first 5 ghosts for these events (say, last January, February, ... May) and later throw them out, so the correct events in the query window (say, this April) would never get generated.
Instead, generate ghosts beginning with the start of the window. The fix in D17644 to number results correctly allows us to do this.
Test Plan:
- Made a query panel showing 5 events, scheduled an event long in the past, did not visit any of the instances of it so they didn't generate concrete objects.
- Before the patch, near-future instances failed to show; after the patch, they show.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11816
Differential Revision: https://secure.phabricator.com/D17645
2017-04-10 06:29:08 -07:00
|
|
|
$start_date,
|
2016-10-03 07:40:01 -07:00
|
|
|
$end_date,
|
|
|
|
$limit + 1);
|
2015-05-31 15:04:48 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
// We're generating events from the beginning and then filtering them
|
|
|
|
// here (instead of only generating events starting at the start date)
|
|
|
|
// because we need to know the proper sequence indexes to generate ghost
|
|
|
|
// events. This may change after RDATE support.
|
|
|
|
if ($start_date) {
|
|
|
|
$start_epoch = $start_date->getEpoch();
|
|
|
|
} else {
|
|
|
|
$start_epoch = null;
|
2016-07-13 09:29:02 -07:00
|
|
|
}
|
2016-04-07 05:10:55 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
foreach ($recurrences as $sequence_index => $sequence_datetime) {
|
|
|
|
if (!$sequence_index) {
|
|
|
|
// This is the parent event, which we already have.
|
|
|
|
continue;
|
|
|
|
}
|
2016-07-13 09:29:02 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
if ($start_epoch) {
|
|
|
|
if ($sequence_datetime->getEpoch() < $start_epoch) {
|
|
|
|
continue;
|
2016-04-07 05:10:55 -07:00
|
|
|
}
|
2015-05-31 15:04:48 -07:00
|
|
|
}
|
2016-07-13 09:29:02 -07:00
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
$events[] = $event->newGhost(
|
|
|
|
$viewer,
|
|
|
|
$sequence_index,
|
|
|
|
$sequence_datetime);
|
2016-07-13 09:29:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: We're slicing results every time because this makes it cheaper
|
|
|
|
// to generate future ghosts. If we already have 100 events that occur
|
|
|
|
// before July 1, we know we never need to generate ghosts after that
|
|
|
|
// because they couldn't possibly ever appear in the result set.
|
|
|
|
|
|
|
|
if ($raw_limit) {
|
|
|
|
if (count($events) > $raw_limit) {
|
2016-10-04 09:47:38 -07:00
|
|
|
$events = msort($events, 'getStartDateTimeEpoch');
|
2016-07-13 09:29:02 -07:00
|
|
|
$events = array_slice($events, 0, $raw_limit, true);
|
2016-10-03 07:40:01 -07:00
|
|
|
$generate_until = last($events)->getEndDateTimeEpoch();
|
2016-07-13 09:29:02 -07:00
|
|
|
}
|
2015-05-31 15:04:48 -07: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
2016-07-07 07:19:58 -07:00
|
|
|
// Now that we're done generating ghost events, we're going to remove any
|
|
|
|
// 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.
|
2015-06-03 13:01:26 -07: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
2016-07-07 07:19:58 -07:00
|
|
|
// 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) {
|
2015-06-03 13:01:26 -07:00
|
|
|
if ($event->getIsGhostEvent()) {
|
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
2016-07-07 07:19:58 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$parent_phid = $event->getInstanceOfEventPHID();
|
|
|
|
$sequence = $event->getSequenceIndex();
|
|
|
|
|
|
|
|
$have_pairs[$parent_phid][$sequence] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, generate a map of all <parentPHID, sequence> events we generated
|
|
|
|
// ghosts for. We need to try to load these if we don't already have them.
|
|
|
|
$map = array();
|
|
|
|
$parent_pairs = array();
|
|
|
|
foreach ($events as $key => $event) {
|
|
|
|
if (!$event->getIsGhostEvent()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$parent_phid = $event->getInstanceOfEventPHID();
|
|
|
|
$sequence = $event->getSequenceIndex();
|
|
|
|
|
|
|
|
// We already loaded the concrete version of this event, so we can just
|
|
|
|
// throw out the ghost and move on.
|
|
|
|
if (isset($have_pairs[$parent_phid][$sequence])) {
|
|
|
|
unset($events[$key]);
|
|
|
|
continue;
|
2015-06-03 13:01:26 -07: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
2016-07-07 07:19:58 -07:00
|
|
|
|
|
|
|
// 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;
|
2015-06-03 13:01:26 -07: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
2016-07-07 07:19:58 -07:00
|
|
|
if ($parent_pairs) {
|
|
|
|
$instances = id(new self())
|
2015-05-31 15:04:48 -07:00
|
|
|
->setViewer($viewer)
|
|
|
|
->setParentQuery($this)
|
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
2016-07-07 07:19:58 -07:00
|
|
|
->withInstanceSequencePairs($parent_pairs)
|
2015-05-31 15:04:48 -07:00
|
|
|
->execute();
|
|
|
|
|
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
2016-07-07 07:19:58 -07:00
|
|
|
foreach ($instances as $instance) {
|
|
|
|
$parent_phid = $instance->getInstanceOfEventPHID();
|
|
|
|
$sequence = $instance->getSequenceIndex();
|
2015-05-31 15:04:48 -07: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
2016-07-07 07:19:58 -07:00
|
|
|
$indexes = idx($map, $parent_phid);
|
|
|
|
$key = idx($indexes, $sequence);
|
|
|
|
|
|
|
|
// Replace the ghost with the corresponding concrete event.
|
|
|
|
$events[$key] = $instance;
|
2015-05-28 17:27:25 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-04 09:47:38 -07:00
|
|
|
$events = msort($events, 'getStartDateTimeEpoch');
|
2016-07-13 09:29:02 -07:00
|
|
|
|
2015-05-07 18:57:28 -07:00
|
|
|
return $events;
|
2014-02-06 10:07:42 -08:00
|
|
|
}
|
|
|
|
|
2015-05-06 11:12:24 -07:00
|
|
|
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) {
|
|
|
|
$parts = parent::buildJoinClauseParts($conn_r);
|
2016-07-13 09:29:02 -07:00
|
|
|
|
2015-05-06 11:12:24 -07:00
|
|
|
if ($this->inviteePHIDs !== null) {
|
|
|
|
$parts[] = qsprintf(
|
|
|
|
$conn_r,
|
2015-05-06 12:41:02 -07:00
|
|
|
'JOIN %T invitee ON invitee.eventPHID = event.phid
|
|
|
|
AND invitee.status != %s',
|
2015-05-06 11:12:24 -07:00
|
|
|
id(new PhabricatorCalendarEventInvitee())->getTableName(),
|
|
|
|
PhabricatorCalendarEventInvitee::STATUS_UNINVITED);
|
|
|
|
}
|
2016-07-13 09:29:02 -07:00
|
|
|
|
2015-05-06 11:12:24 -07:00
|
|
|
return $parts;
|
|
|
|
}
|
|
|
|
|
2016-04-07 05:10:55 -07:00
|
|
|
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
|
|
|
$where = parent::buildWhereClauseParts($conn);
|
2014-02-06 10:07:42 -08:00
|
|
|
|
2016-10-06 12:31:57 -07:00
|
|
|
if ($this->ids !== null) {
|
2014-02-06 10:07:42 -08:00
|
|
|
$where[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
2015-05-06 11:12:24 -07:00
|
|
|
'event.id IN (%Ld)',
|
2014-02-06 10:07:42 -08:00
|
|
|
$this->ids);
|
|
|
|
}
|
|
|
|
|
2016-10-06 12:31:57 -07:00
|
|
|
if ($this->phids !== null) {
|
2014-02-06 10:10:43 -08:00
|
|
|
$where[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
2015-05-06 11:12:24 -07:00
|
|
|
'event.phid IN (%Ls)',
|
2014-02-06 10:10:43 -08:00
|
|
|
$this->phids);
|
|
|
|
}
|
|
|
|
|
2016-07-11 15:29:11 -07:00
|
|
|
// NOTE: The date ranges we query for are larger than the requested ranges
|
|
|
|
// because we need to catch all-day events. We'll refine this range later
|
|
|
|
// after adjusting the visible range of events we load.
|
|
|
|
|
2014-02-06 10:10:18 -08:00
|
|
|
if ($this->rangeBegin) {
|
|
|
|
$where[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
Migrate Calendar away from stored-epoch fields
Summary:
Ref T10747. This deprecates "dateFrom", "dateTo", "allDayDateFrom", "allDayDateTo", and "recurrenceEndDate".
They are replaced with "utc*Epoch" fields (for querying) and CalendarDateTime objects (for start, end, until). These objects can represent the full range of dates and times expressible in ICS format, allowing us to import a wider range of ICS events.
Test Plan:
Ran migrations, viewed/edited Calendar, didn't catch anything catastrophcially broken.
This likely needs some followups, I'll keep it local for a bit until I'm confident I didn't break anything too catastrophically. I'm retaining the old data for now so we can likely fix things if it turns out there is some sort of issue.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T10747
Differential Revision: https://secure.phabricator.com/D16664
2016-10-04 12:03:20 -07:00
|
|
|
'(event.utcUntilEpoch >= %d) OR (event.utcUntilEpoch IS NULL)',
|
2016-07-11 15:29:11 -07:00
|
|
|
$this->rangeBegin - phutil_units('16 hours in seconds'));
|
2014-02-06 10:10:18 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->rangeEnd) {
|
2014-02-06 10:07:42 -08:00
|
|
|
$where[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
Migrate Calendar away from stored-epoch fields
Summary:
Ref T10747. This deprecates "dateFrom", "dateTo", "allDayDateFrom", "allDayDateTo", and "recurrenceEndDate".
They are replaced with "utc*Epoch" fields (for querying) and CalendarDateTime objects (for start, end, until). These objects can represent the full range of dates and times expressible in ICS format, allowing us to import a wider range of ICS events.
Test Plan:
Ran migrations, viewed/edited Calendar, didn't catch anything catastrophcially broken.
This likely needs some followups, I'll keep it local for a bit until I'm confident I didn't break anything too catastrophically. I'm retaining the old data for now so we can likely fix things if it turns out there is some sort of issue.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T10747
Differential Revision: https://secure.phabricator.com/D16664
2016-10-04 12:03:20 -07:00
|
|
|
'event.utcInitialEpoch <= %d',
|
2016-07-11 15:29:11 -07:00
|
|
|
$this->rangeEnd + phutil_units('16 hours in seconds'));
|
2014-02-06 10:07:42 -08:00
|
|
|
}
|
|
|
|
|
Begin navigating the mess that is edits to recurring events
Summary:
Ref T11804. This puts us on a path toward some kind of reasonable behavior here.
Currently, cancelling recurring events makes approximately zero sense ever in any situation.
Instead, give users the choice to cancel just the instance, or all future events. This is similar to Calendar.app. (Google Calendar has a third option, "All Events", which I may implement).
When the user picks something, basically do that.
The particulars of "do that" are messy. We have to split the series into two different series, stop the first series early, then edit the second series. Then we need to update any concrete events that are now part of the second series.
This code will get less junk in the next couple of diffs (I hope?) since I need to make it apply to edits, too, but this was a little easier to get started with.
Test Plan:
Cancelled an instance of an event; cancelled "All future events".
Both of them more or less worked in a reasonble way.
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11804
Differential Revision: https://secure.phabricator.com/D16778
2016-10-31 12:47:39 -07:00
|
|
|
if ($this->utcInitialEpochMin !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'event.utcInitialEpoch >= %d',
|
|
|
|
$this->utcInitialEpochMin);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->utcInitialEpochMax !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'event.utcInitialEpoch <= %d',
|
|
|
|
$this->utcInitialEpochMax);
|
|
|
|
}
|
|
|
|
|
2015-05-06 11:12:24 -07:00
|
|
|
if ($this->inviteePHIDs !== null) {
|
2014-02-06 10:07:42 -08:00
|
|
|
$where[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
2015-05-06 11:12:24 -07:00
|
|
|
'invitee.inviteePHID IN (%Ls)',
|
|
|
|
$this->inviteePHIDs);
|
2014-02-06 10:07:42 -08:00
|
|
|
}
|
|
|
|
|
2016-10-06 12:31:57 -07:00
|
|
|
if ($this->hostPHIDs !== null) {
|
2014-02-06 10:10:18 -08:00
|
|
|
$where[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
2016-07-14 12:54:36 -07:00
|
|
|
'event.hostPHID IN (%Ls)',
|
|
|
|
$this->hostPHIDs);
|
2014-02-06 10:10:18 -08:00
|
|
|
}
|
|
|
|
|
Canceling calendar events should deactivate the event
Summary: Closes T7943, Canceling calendar event should deactivate the event instead of destroying data.
Test Plan: Create an event, cancel it, see changed status icon, query for active events, event should not appear, query for deactivated events, event should appear in results.
Reviewers: #blessed_reviewers, epriestley
Reviewed By: #blessed_reviewers, epriestley
Subscribers: Korvin, epriestley
Maniphest Tasks: T7943
Differential Revision: https://secure.phabricator.com/D12604
2015-04-29 08:39:39 -07:00
|
|
|
if ($this->isCancelled !== null) {
|
|
|
|
$where[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
2015-05-06 11:12:24 -07:00
|
|
|
'event.isCancelled = %d',
|
Canceling calendar events should deactivate the event
Summary: Closes T7943, Canceling calendar event should deactivate the event instead of destroying data.
Test Plan: Create an event, cancel it, see changed status icon, query for active events, event should not appear, query for deactivated events, event should appear in results.
Reviewers: #blessed_reviewers, epriestley
Reviewed By: #blessed_reviewers, epriestley
Subscribers: Korvin, epriestley
Maniphest Tasks: T7943
Differential Revision: https://secure.phabricator.com/D12604
2015-04-29 08:39:39 -07:00
|
|
|
(int)$this->isCancelled);
|
|
|
|
}
|
|
|
|
|
2015-06-03 09:27:39 -07:00
|
|
|
if ($this->eventsWithNoParent == true) {
|
|
|
|
$where[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
2015-06-03 09:27:39 -07:00
|
|
|
'event.instanceOfEventPHID IS NULL');
|
|
|
|
}
|
|
|
|
|
2015-05-31 15:04:48 -07:00
|
|
|
if ($this->instanceSequencePairs !== null) {
|
|
|
|
$sql = array();
|
|
|
|
|
|
|
|
foreach ($this->instanceSequencePairs as $pair) {
|
|
|
|
$sql[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
2015-05-31 15:04:48 -07:00
|
|
|
'(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)',
|
|
|
|
$pair[0],
|
|
|
|
$pair[1]);
|
|
|
|
}
|
2016-04-07 05:10:55 -07:00
|
|
|
|
2015-05-31 15:04:48 -07:00
|
|
|
$where[] = qsprintf(
|
2016-04-07 05:10:55 -07:00
|
|
|
$conn,
|
2018-11-15 05:53:34 -08:00
|
|
|
'%LO',
|
|
|
|
$sql);
|
2015-05-31 15:04:48 -07: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
2016-07-07 07:19:58 -07:00
|
|
|
if ($this->isStub !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'event.isStub = %d',
|
|
|
|
(int)$this->isStub);
|
|
|
|
}
|
|
|
|
|
2016-10-06 12:31:57 -07:00
|
|
|
if ($this->parentEventPHIDs !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'event.instanceOfEventPHID IN (%Ls)',
|
|
|
|
$this->parentEventPHIDs);
|
|
|
|
}
|
|
|
|
|
2016-10-11 11:45:57 -07:00
|
|
|
if ($this->importSourcePHIDs !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'event.importSourcePHID IN (%Ls)',
|
|
|
|
$this->importSourcePHIDs);
|
|
|
|
}
|
|
|
|
|
2016-10-13 08:01:08 -07:00
|
|
|
if ($this->importAuthorPHIDs !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'event.importAuthorPHID IN (%Ls)',
|
|
|
|
$this->importAuthorPHIDs);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->importUIDs !== null) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'event.importUID IN (%Ls)',
|
|
|
|
$this->importUIDs);
|
|
|
|
}
|
|
|
|
|
2016-10-31 18:01:33 -07:00
|
|
|
if ($this->isImported !== null) {
|
|
|
|
if ($this->isImported) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'event.importSourcePHID IS NOT NULL');
|
|
|
|
} else {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn,
|
|
|
|
'event.importSourcePHID IS NULL');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-07 05:10:55 -07:00
|
|
|
return $where;
|
2014-02-06 10:07:42 -08:00
|
|
|
}
|
|
|
|
|
2015-05-06 11:12:24 -07:00
|
|
|
protected function getPrimaryTableAlias() {
|
|
|
|
return 'event';
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function shouldGroupQueryResultRows() {
|
|
|
|
if ($this->inviteePHIDs !== null) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return parent::shouldGroupQueryResultRows();
|
|
|
|
}
|
|
|
|
|
2014-02-06 10:07:42 -08:00
|
|
|
public function getQueryApplicationClass() {
|
2014-07-23 10:03:09 +10:00
|
|
|
return 'PhabricatorCalendarApplication';
|
2014-02-06 10:07:42 -08:00
|
|
|
}
|
|
|
|
|
2015-04-29 13:51:09 -07:00
|
|
|
protected function willFilterPage(array $events) {
|
2015-06-02 19:44:31 -07:00
|
|
|
$instance_of_event_phids = array();
|
|
|
|
$recurring_events = array();
|
|
|
|
$viewer = $this->getViewer();
|
2015-05-26 14:28:07 -07:00
|
|
|
|
2016-07-13 09:29:02 -07:00
|
|
|
$events = $this->getEventsInRange($events);
|
2015-05-26 14:28:07 -07:00
|
|
|
|
2016-10-11 11:45:57 -07:00
|
|
|
$import_phids = array();
|
|
|
|
foreach ($events as $event) {
|
|
|
|
$import_phid = $event->getImportSourcePHID();
|
|
|
|
if ($import_phid !== null) {
|
|
|
|
$import_phids[$import_phid] = $import_phid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($import_phids) {
|
|
|
|
$imports = id(new PhabricatorCalendarImportQuery())
|
|
|
|
->setParentQuery($this)
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withPHIDs($import_phids)
|
|
|
|
->execute();
|
2016-10-12 11:31:21 -07:00
|
|
|
$imports = mpull($imports, null, 'getPHID');
|
2016-10-11 11:45:57 -07:00
|
|
|
} else {
|
2016-10-12 11:31:21 -07:00
|
|
|
$imports = array();
|
2016-10-11 11:45:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($events as $key => $event) {
|
|
|
|
$import_phid = $event->getImportSourcePHID();
|
|
|
|
if ($import_phid === null) {
|
|
|
|
$event->attachImportSource(null);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$import = idx($imports, $import_phid);
|
|
|
|
if (!$import) {
|
|
|
|
unset($events[$key]);
|
|
|
|
$this->didRejectResult($event);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$event->attachImportSource($import);
|
|
|
|
}
|
|
|
|
|
2015-04-29 13:51:09 -07:00
|
|
|
$phids = array();
|
|
|
|
|
|
|
|
foreach ($events as $event) {
|
|
|
|
$phids[] = $event->getPHID();
|
2015-06-02 19:44:31 -07:00
|
|
|
$instance_of = $event->getInstanceOfEventPHID();
|
|
|
|
|
|
|
|
if ($instance_of) {
|
2015-06-03 09:27:39 -07:00
|
|
|
$instance_of_event_phids[] = $instance_of;
|
2015-06-02 19:44:31 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count($instance_of_event_phids) > 0) {
|
|
|
|
$recurring_events = id(new PhabricatorCalendarEventQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withPHIDs($instance_of_event_phids)
|
2015-06-03 09:27:39 -07:00
|
|
|
->withEventsWithNoParent(true)
|
2015-06-02 19:44:31 -07:00
|
|
|
->execute();
|
|
|
|
|
|
|
|
$recurring_events = mpull($recurring_events, null, 'getPHID');
|
2015-04-29 13:51:09 -07:00
|
|
|
}
|
|
|
|
|
2015-05-29 07:17:09 -07:00
|
|
|
if ($events) {
|
|
|
|
$invitees = id(new PhabricatorCalendarEventInviteeQuery())
|
2015-06-02 19:44:31 -07:00
|
|
|
->setViewer($viewer)
|
2015-05-29 07:17:09 -07:00
|
|
|
->withEventPHIDs($phids)
|
|
|
|
->execute();
|
|
|
|
$invitees = mgroup($invitees, 'getEventPHID');
|
|
|
|
} else {
|
|
|
|
$invitees = array();
|
|
|
|
}
|
2015-04-29 13:51:09 -07:00
|
|
|
|
2015-06-02 19:44:31 -07:00
|
|
|
foreach ($events as $key => $event) {
|
2015-04-29 13:51:09 -07:00
|
|
|
$event_invitees = idx($invitees, $event->getPHID(), array());
|
|
|
|
$event->attachInvitees($event_invitees);
|
2015-06-02 19:44:31 -07:00
|
|
|
|
|
|
|
$instance_of = $event->getInstanceOfEventPHID();
|
|
|
|
if (!$instance_of) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$parent = idx($recurring_events, $instance_of);
|
|
|
|
|
|
|
|
// should never get here
|
|
|
|
if (!$parent) {
|
|
|
|
unset($events[$key]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$event->attachParentEvent($parent);
|
|
|
|
|
|
|
|
if ($this->isCancelled !== null) {
|
|
|
|
if ($event->getIsCancelled() != $this->isCancelled) {
|
|
|
|
unset($events[$key]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2015-04-29 13:51:09 -07:00
|
|
|
}
|
|
|
|
|
2016-10-04 09:47:38 -07:00
|
|
|
$events = msort($events, 'getStartDateTimeEpoch');
|
2015-05-26 14:28:07 -07:00
|
|
|
|
Improve Calendar event behavior for group invites
Summary:
Ref T11816. Projects can be invited to an event, but the UI is currently fairly agnostic about them.
Instead, introduce the idea of "RSVPs", which are basically invites for you as an individual or for any group you're a part of. When we go to check if you're invited, we check for you individually first, then check for any groups you belong to if you haven't already accepted/declined.
On the calendar detail page:
- Show the quick "Join" / "Decline" buttons if any project you're a member of is invited.
- If you're invited, highlight any projects which you're a member of to make that more clear.
On other calendar views:
- If you're invited as part of a project, show the "multiple users" icon.
- If it's just you, continue showing the "add one user" icon.
Test Plan: Viewed month view, day view, detail view. Invited groups and individuals. Invited "Dog Project", accepted invite as user "Dog".
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T11816
Differential Revision: https://secure.phabricator.com/D16868
2016-11-15 11:01:05 -08:00
|
|
|
if ($this->needRSVPs) {
|
|
|
|
$rsvp_phids = $this->needRSVPs;
|
|
|
|
$project_type = PhabricatorProjectProjectPHIDType::TYPECONST;
|
|
|
|
|
|
|
|
$project_phids = array();
|
|
|
|
foreach ($events as $event) {
|
|
|
|
foreach ($event->getInvitees() as $invitee) {
|
|
|
|
$invitee_phid = $invitee->getInviteePHID();
|
|
|
|
if (phid_get_type($invitee_phid) == $project_type) {
|
|
|
|
$project_phids[] = $invitee_phid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($project_phids) {
|
|
|
|
$member_type = PhabricatorProjectMaterializedMemberEdgeType::EDGECONST;
|
|
|
|
|
|
|
|
$query = id(new PhabricatorEdgeQuery())
|
|
|
|
->withSourcePHIDs($project_phids)
|
|
|
|
->withEdgeTypes(array($member_type))
|
|
|
|
->withDestinationPHIDs($rsvp_phids);
|
|
|
|
|
|
|
|
$edges = $query->execute();
|
|
|
|
|
|
|
|
$project_map = array();
|
|
|
|
foreach ($edges as $src => $types) {
|
|
|
|
foreach ($types as $type => $dsts) {
|
|
|
|
foreach ($dsts as $dst => $edge) {
|
|
|
|
$project_map[$dst][] = $src;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$project_map = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$membership_map = array();
|
|
|
|
foreach ($rsvp_phids as $rsvp_phid) {
|
|
|
|
$membership_map[$rsvp_phid] = array();
|
|
|
|
$membership_map[$rsvp_phid][] = $rsvp_phid;
|
|
|
|
|
|
|
|
$project_phids = idx($project_map, $rsvp_phid);
|
|
|
|
if ($project_phids) {
|
|
|
|
foreach ($project_phids as $project_phid) {
|
|
|
|
$membership_map[$rsvp_phid][] = $project_phid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($events as $event) {
|
|
|
|
$invitees = $event->getInvitees();
|
|
|
|
$invitees = mpull($invitees, null, 'getInviteePHID');
|
|
|
|
|
|
|
|
$rsvp_map = array();
|
|
|
|
foreach ($rsvp_phids as $rsvp_phid) {
|
|
|
|
$membership_phids = $membership_map[$rsvp_phid];
|
|
|
|
$rsvps = array_select_keys($invitees, $membership_phids);
|
|
|
|
$rsvp_map[$rsvp_phid] = $rsvps;
|
|
|
|
}
|
|
|
|
|
|
|
|
$event->attachRSVPs($rsvp_map);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-29 13:51:09 -07:00
|
|
|
return $events;
|
|
|
|
}
|
|
|
|
|
2016-07-13 09:29:02 -07:00
|
|
|
private function getEventsInRange(array $events) {
|
|
|
|
$range_start = $this->rangeBegin;
|
|
|
|
$range_end = $this->rangeEnd;
|
|
|
|
|
|
|
|
foreach ($events as $key => $event) {
|
2016-10-04 09:47:38 -07:00
|
|
|
$event_start = $event->getStartDateTimeEpoch();
|
|
|
|
$event_end = $event->getEndDateTimeEpoch();
|
2016-07-13 09:29:02 -07:00
|
|
|
|
|
|
|
if ($range_start && $event_end < $range_start) {
|
|
|
|
unset($events[$key]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($range_end && $event_start > $range_end) {
|
|
|
|
unset($events[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $events;
|
|
|
|
}
|
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
private function getRecurrenceWindowStart(
|
|
|
|
PhabricatorCalendarEvent $event,
|
|
|
|
$generate_from) {
|
|
|
|
|
|
|
|
if (!$generate_from) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PhutilCalendarAbsoluteDateTime::newFromEpoch($generate_from);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getRecurrenceWindowEnd(
|
|
|
|
PhabricatorCalendarEvent $event,
|
|
|
|
$generate_until) {
|
|
|
|
|
|
|
|
$end_epochs = array();
|
|
|
|
if ($generate_until) {
|
|
|
|
$end_epochs[] = $generate_until;
|
|
|
|
}
|
|
|
|
|
|
|
|
$until_epoch = $event->getUntilDateTimeEpoch();
|
|
|
|
if ($until_epoch) {
|
|
|
|
$end_epochs[] = $until_epoch;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$end_epochs) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return PhutilCalendarAbsoluteDateTime::newFromEpoch(min($end_epochs));
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getRecurrenceLimit(
|
|
|
|
PhabricatorCalendarEvent $event,
|
|
|
|
$raw_limit) {
|
|
|
|
|
2016-10-25 10:31:10 -07:00
|
|
|
$count = $event->getRecurrenceCount();
|
|
|
|
if ($count && ($count <= $raw_limit)) {
|
|
|
|
return ($count - 1);
|
|
|
|
}
|
|
|
|
|
2016-10-03 07:40:01 -07:00
|
|
|
return $raw_limit;
|
|
|
|
}
|
|
|
|
|
2014-02-06 10:07:42 -08:00
|
|
|
}
|