mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-05 03:18:25 +01:00
d3a84687ba
Summary: Closes T8371, Query for recurring event parents should accept a "not a child of anything" clause Test Plan: querying for a list of events with recurring events should still work the same. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8371 Differential Revision: https://secure.phabricator.com/D13140
408 lines
10 KiB
PHP
408 lines
10 KiB
PHP
<?php
|
|
|
|
final class PhabricatorCalendarEventQuery
|
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
|
|
|
private $ids;
|
|
private $phids;
|
|
private $rangeBegin;
|
|
private $rangeEnd;
|
|
private $inviteePHIDs;
|
|
private $creatorPHIDs;
|
|
private $isCancelled;
|
|
private $eventsWithNoParent;
|
|
private $instanceSequencePairs;
|
|
|
|
private $generateGhosts = false;
|
|
|
|
public function setGenerateGhosts($generate_ghosts) {
|
|
$this->generateGhosts = $generate_ghosts;
|
|
return $this;
|
|
}
|
|
|
|
public function withIDs(array $ids) {
|
|
$this->ids = $ids;
|
|
return $this;
|
|
}
|
|
|
|
public function withPHIDs(array $phids) {
|
|
$this->phids = $phids;
|
|
return $this;
|
|
}
|
|
|
|
public function withDateRange($begin, $end) {
|
|
$this->rangeBegin = $begin;
|
|
$this->rangeEnd = $end;
|
|
return $this;
|
|
}
|
|
|
|
public function withInvitedPHIDs(array $phids) {
|
|
$this->inviteePHIDs = $phids;
|
|
return $this;
|
|
}
|
|
|
|
public function withCreatorPHIDs(array $phids) {
|
|
$this->creatorPHIDs = $phids;
|
|
return $this;
|
|
}
|
|
|
|
public function withIsCancelled($is_cancelled) {
|
|
$this->isCancelled = $is_cancelled;
|
|
return $this;
|
|
}
|
|
|
|
public function withEventsWithNoParent($events_with_no_parent) {
|
|
$this->eventsWithNoParent = $events_with_no_parent;
|
|
return $this;
|
|
}
|
|
|
|
public function withInstanceSequencePairs(array $pairs) {
|
|
$this->instanceSequencePairs = $pairs;
|
|
return $this;
|
|
}
|
|
|
|
protected function getDefaultOrderVector() {
|
|
return array('start', 'id');
|
|
}
|
|
|
|
public function getOrderableColumns() {
|
|
return array(
|
|
'start' => array(
|
|
'table' => $this->getPrimaryTableAlias(),
|
|
'column' => 'dateFrom',
|
|
'reverse' => true,
|
|
'type' => 'int',
|
|
'unique' => false,
|
|
),
|
|
) + parent::getOrderableColumns();
|
|
}
|
|
|
|
protected function getPagingValueMap($cursor, array $keys) {
|
|
$event = $this->loadCursorObject($cursor);
|
|
return array(
|
|
'start' => $event->getDateFrom(),
|
|
'id' => $event->getID(),
|
|
);
|
|
}
|
|
|
|
protected function loadPage() {
|
|
$table = new PhabricatorCalendarEvent();
|
|
$conn_r = $table->establishConnection('r');
|
|
$viewer = $this->getViewer();
|
|
|
|
$data = queryfx_all(
|
|
$conn_r,
|
|
'SELECT event.* FROM %T event %Q %Q %Q %Q %Q',
|
|
$table->getTableName(),
|
|
$this->buildJoinClause($conn_r),
|
|
$this->buildWhereClause($conn_r),
|
|
$this->buildGroupClause($conn_r),
|
|
$this->buildOrderClause($conn_r),
|
|
$this->buildLimitClause($conn_r));
|
|
|
|
$events = $table->loadAllFromArray($data);
|
|
|
|
foreach ($events as $event) {
|
|
$event->applyViewerTimezone($this->getViewer());
|
|
}
|
|
|
|
if (!$this->generateGhosts) {
|
|
return $events;
|
|
}
|
|
|
|
$map = array();
|
|
$instance_sequence_pairs = array();
|
|
|
|
foreach ($events as $key => $event) {
|
|
$sequence_start = 0;
|
|
$sequence_end = null;
|
|
$duration = $event->getDateTo() - $event->getDateFrom();
|
|
$end = null;
|
|
|
|
$instance_of = $event->getInstanceOfEventPHID();
|
|
|
|
if ($instance_of == null && $this->isCancelled !== null) {
|
|
if ($event->getIsCancelled() != $this->isCancelled) {
|
|
unset($events[$key]);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ($event->getIsRecurring() && $instance_of == null) {
|
|
$frequency = $event->getFrequencyUnit();
|
|
$modify_key = '+1 '.$frequency;
|
|
|
|
if ($this->rangeBegin && $this->rangeBegin > $event->getDateFrom()) {
|
|
$max_date = $this->rangeBegin - $duration;
|
|
$date = $event->getDateFrom();
|
|
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
|
|
|
|
while ($date < $max_date) {
|
|
// TODO: optimize this to not loop through all off-screen events
|
|
$sequence_start++;
|
|
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
|
|
$date = $datetime->modify($modify_key)->format('U');
|
|
}
|
|
|
|
$start = $this->rangeBegin;
|
|
} else {
|
|
$start = $event->getDateFrom() - $duration;
|
|
}
|
|
|
|
$date = $start;
|
|
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
|
|
|
|
if (($this->rangeEnd && $event->getRecurrenceEndDate()) &&
|
|
$this->rangeEnd < $event->getRecurrenceEndDate()) {
|
|
$end = $this->rangeEnd;
|
|
} else if ($event->getRecurrenceEndDate()) {
|
|
$end = $event->getRecurrenceEndDate();
|
|
} else if ($this->rangeEnd) {
|
|
$end = $this->rangeEnd;
|
|
}
|
|
|
|
if ($end) {
|
|
$sequence_end = $sequence_start;
|
|
while ($date < $end) {
|
|
$sequence_end++;
|
|
$datetime->modify($modify_key);
|
|
$date = $datetime->format('U');
|
|
}
|
|
} else {
|
|
$sequence_end = $this->getRawResultLimit() + $sequence_start;
|
|
}
|
|
|
|
$sequence_start = max(1, $sequence_start);
|
|
|
|
for ($index = $sequence_start; $index < $sequence_end; $index++) {
|
|
$instance_sequence_pairs[] = array($event->getPHID(), $index);
|
|
$events[] = $event->generateNthGhost($index, $viewer);
|
|
|
|
$last_key = last_key($events);
|
|
$map[$event->getPHID()][$index] = $last_key;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($instance_sequence_pairs) > 0) {
|
|
$sub_query = id(new PhabricatorCalendarEventQuery())
|
|
->setViewer($viewer)
|
|
->setParentQuery($this)
|
|
->withInstanceSequencePairs($instance_sequence_pairs)
|
|
->execute();
|
|
|
|
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();
|
|
foreach ($events as $key => $event) {
|
|
if ($event->getIsGhostEvent()) {
|
|
continue;
|
|
}
|
|
if (isset($id_map[$event->getID()])) {
|
|
unset($events[$key]);
|
|
} else {
|
|
$id_map[$event->getID()] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $events;
|
|
}
|
|
|
|
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn_r) {
|
|
$parts = parent::buildJoinClauseParts($conn_r);
|
|
if ($this->inviteePHIDs !== null) {
|
|
$parts[] = qsprintf(
|
|
$conn_r,
|
|
'JOIN %T invitee ON invitee.eventPHID = event.phid
|
|
AND invitee.status != %s',
|
|
id(new PhabricatorCalendarEventInvitee())->getTableName(),
|
|
PhabricatorCalendarEventInvitee::STATUS_UNINVITED);
|
|
}
|
|
return $parts;
|
|
}
|
|
|
|
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
|
$where = array();
|
|
|
|
if ($this->ids) {
|
|
$where[] = qsprintf(
|
|
$conn_r,
|
|
'event.id IN (%Ld)',
|
|
$this->ids);
|
|
}
|
|
|
|
if ($this->phids) {
|
|
$where[] = qsprintf(
|
|
$conn_r,
|
|
'event.phid IN (%Ls)',
|
|
$this->phids);
|
|
}
|
|
|
|
if ($this->rangeBegin) {
|
|
$where[] = qsprintf(
|
|
$conn_r,
|
|
'event.dateTo >= %d OR event.isRecurring = 1',
|
|
$this->rangeBegin);
|
|
}
|
|
|
|
if ($this->rangeEnd) {
|
|
$where[] = qsprintf(
|
|
$conn_r,
|
|
'event.dateFrom <= %d',
|
|
$this->rangeEnd);
|
|
}
|
|
|
|
if ($this->inviteePHIDs !== null) {
|
|
$where[] = qsprintf(
|
|
$conn_r,
|
|
'invitee.inviteePHID IN (%Ls)',
|
|
$this->inviteePHIDs);
|
|
}
|
|
|
|
if ($this->creatorPHIDs) {
|
|
$where[] = qsprintf(
|
|
$conn_r,
|
|
'event.userPHID IN (%Ls)',
|
|
$this->creatorPHIDs);
|
|
}
|
|
|
|
if ($this->isCancelled !== null) {
|
|
$where[] = qsprintf(
|
|
$conn_r,
|
|
'event.isCancelled = %d',
|
|
(int)$this->isCancelled);
|
|
}
|
|
|
|
if ($this->eventsWithNoParent == true) {
|
|
$where[] = qsprintf(
|
|
$conn_r,
|
|
'event.instanceOfEventPHID IS NULL');
|
|
}
|
|
|
|
if ($this->instanceSequencePairs !== null) {
|
|
$sql = array();
|
|
|
|
foreach ($this->instanceSequencePairs as $pair) {
|
|
$sql[] = qsprintf(
|
|
$conn_r,
|
|
'(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)',
|
|
$pair[0],
|
|
$pair[1]);
|
|
}
|
|
$where[] = qsprintf(
|
|
$conn_r,
|
|
'%Q',
|
|
implode(' OR ', $sql));
|
|
}
|
|
|
|
$where[] = $this->buildPagingClause($conn_r);
|
|
|
|
return $this->formatWhereClause($where);
|
|
}
|
|
|
|
protected function getPrimaryTableAlias() {
|
|
return 'event';
|
|
}
|
|
|
|
protected function shouldGroupQueryResultRows() {
|
|
if ($this->inviteePHIDs !== null) {
|
|
return true;
|
|
}
|
|
return parent::shouldGroupQueryResultRows();
|
|
}
|
|
|
|
protected function getApplicationSearchObjectPHIDColumn() {
|
|
return 'event.phid';
|
|
}
|
|
|
|
public function getQueryApplicationClass() {
|
|
return 'PhabricatorCalendarApplication';
|
|
}
|
|
|
|
|
|
protected function willFilterPage(array $events) {
|
|
$range_start = $this->rangeBegin;
|
|
$range_end = $this->rangeEnd;
|
|
$instance_of_event_phids = array();
|
|
$recurring_events = array();
|
|
$viewer = $this->getViewer();
|
|
|
|
foreach ($events as $key => $event) {
|
|
$event_start = $event->getDateFrom();
|
|
$event_end = $event->getDateTo();
|
|
|
|
if ($range_start && $event_end < $range_start) {
|
|
unset($events[$key]);
|
|
}
|
|
if ($range_end && $event_start > $range_end) {
|
|
unset($events[$key]);
|
|
}
|
|
}
|
|
|
|
$phids = array();
|
|
|
|
foreach ($events as $event) {
|
|
$phids[] = $event->getPHID();
|
|
$instance_of = $event->getInstanceOfEventPHID();
|
|
|
|
if ($instance_of) {
|
|
$instance_of_event_phids[] = $instance_of;
|
|
}
|
|
}
|
|
|
|
if (count($instance_of_event_phids) > 0) {
|
|
$recurring_events = id(new PhabricatorCalendarEventQuery())
|
|
->setViewer($viewer)
|
|
->withPHIDs($instance_of_event_phids)
|
|
->withEventsWithNoParent(true)
|
|
->execute();
|
|
|
|
$recurring_events = mpull($recurring_events, null, 'getPHID');
|
|
}
|
|
|
|
if ($events) {
|
|
$invitees = id(new PhabricatorCalendarEventInviteeQuery())
|
|
->setViewer($viewer)
|
|
->withEventPHIDs($phids)
|
|
->execute();
|
|
$invitees = mgroup($invitees, 'getEventPHID');
|
|
} else {
|
|
$invitees = array();
|
|
}
|
|
|
|
foreach ($events as $key => $event) {
|
|
$event_invitees = idx($invitees, $event->getPHID(), array());
|
|
$event->attachInvitees($event_invitees);
|
|
|
|
$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;
|
|
}
|
|
}
|
|
}
|
|
|
|
$events = msort($events, 'getDateFrom');
|
|
|
|
return $events;
|
|
}
|
|
|
|
}
|