Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F36623721
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
43 KB
Subscribers
None
View Options
diff --git a/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php b/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php
index a1264693b..0bb409c0b 100644
--- a/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php
@@ -1,97 +1,100 @@
<?php
final class PhabricatorCalendarExportICSController
extends PhabricatorCalendarController {
public function shouldRequireLogin() {
// Export URIs are available if you know the secret key. We can't do any
// other kind of authentication because third-party applications like
// Google Calendar and Calendar.app need to be able to fetch these URIs.
return false;
}
public function handleRequest(AphrontRequest $request) {
$omnipotent = PhabricatorUser::getOmnipotentUser();
// NOTE: We're using the omnipotent viewer to fetch the export, but the
// URI must contain the secret key. Once we load the export we'll figure
// out who the effective viewer is.
$export = id(new PhabricatorCalendarExportQuery())
->setViewer($omnipotent)
->withSecretKeys(array($request->getURIData('secretKey')))
->executeOne();
if (!$export) {
return new Aphront404Response();
}
if ($export->getIsDisabled()) {
return new Aphront404Response();
}
$author = id(new PhabricatorPeopleQuery())
->setViewer($omnipotent)
->withPHIDs(array($export->getAuthorPHID()))
->needUserSettings(true)
->executeOne();
if (!$author) {
return new Aphront404Response();
}
$mode = $export->getPolicyMode();
switch ($mode) {
case PhabricatorCalendarExport::MODE_PUBLIC:
$viewer = new PhabricatorUser();
break;
case PhabricatorCalendarExport::MODE_PRIVILEGED:
$viewer = $author;
break;
default:
throw new Exception(
pht(
'This export has an invalid mode ("%s").',
$mode));
}
$engine = id(new PhabricatorCalendarEventSearchEngine())
->setViewer($viewer);
$query_key = $export->getQueryKey();
$saved = id(new PhabricatorSavedQueryQuery())
->setViewer($omnipotent)
->withEngineClassNames(array(get_class($engine)))
->withQueryKeys(array($query_key))
->executeOne();
if (!$saved) {
$saved = $engine->buildSavedQueryFromBuiltin($query_key);
}
if (!$saved) {
return new Aphront404Response();
}
$saved = clone $saved;
// Mark this as a query for export, so we get the correct ghost/recurring
// behaviors. We also want to load all matching events.
$saved->setParameter('export', true);
$saved->setParameter('limit', 0xFFFF);
// Remove any range constraints. We always export all matching events into
// ICS files.
$saved->setParameter('rangeStart', null);
$saved->setParameter('rangeEnd', null);
$saved->setParameter('upcoming', null);
+ // The "month" and "day" display modes imply time ranges.
+ $saved->setParameter('display', 'list');
+
$query = $engine->buildQueryFromSavedQuery($saved);
$events = $query
->setViewer($viewer)
->execute();
return $this->newICSResponse(
$viewer,
$export->getICSFilename(),
$events);
}
}
diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
index a8d1ccdc1..bf1d9b43e 100644
--- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
@@ -1,754 +1,753 @@
<?php
final class PhabricatorCalendarEventQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $rangeBegin;
private $rangeEnd;
private $inviteePHIDs;
private $hostPHIDs;
private $isCancelled;
private $eventsWithNoParent;
private $instanceSequencePairs;
private $isStub;
private $parentEventPHIDs;
private $importSourcePHIDs;
private $importAuthorPHIDs;
private $importUIDs;
private $utcInitialEpochMin;
private $utcInitialEpochMax;
private $isImported;
private $needRSVPs;
private $generateGhosts = false;
public function newResultObject() {
return new PhabricatorCalendarEvent();
}
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 withUTCInitialEpochBetween($min, $max) {
$this->utcInitialEpochMin = $min;
$this->utcInitialEpochMax = $max;
return $this;
}
public function withInvitedPHIDs(array $phids) {
$this->inviteePHIDs = $phids;
return $this;
}
public function withHostPHIDs(array $phids) {
$this->hostPHIDs = $phids;
return $this;
}
public function withIsCancelled($is_cancelled) {
$this->isCancelled = $is_cancelled;
return $this;
}
public function withIsStub($is_stub) {
$this->isStub = $is_stub;
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;
}
public function withParentEventPHIDs(array $parent_phids) {
$this->parentEventPHIDs = $parent_phids;
return $this;
}
public function withImportSourcePHIDs(array $import_phids) {
$this->importSourcePHIDs = $import_phids;
return $this;
}
public function withImportAuthorPHIDs(array $author_phids) {
$this->importAuthorPHIDs = $author_phids;
return $this;
}
public function withImportUIDs(array $uids) {
$this->importUIDs = $uids;
return $this;
}
public function withIsImported($is_imported) {
$this->isImported = $is_imported;
return $this;
}
public function needRSVPs(array $phids) {
$this->needRSVPs = $phids;
return $this;
}
protected function getDefaultOrderVector() {
return array('start', 'id');
}
public function getBuiltinOrders() {
return array(
'start' => array(
'vector' => array('start', 'id'),
'name' => pht('Event Start'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return array(
'start' => array(
'table' => $this->getPrimaryTableAlias(),
'column' => 'utcInitialEpoch',
'reverse' => true,
'type' => 'int',
'unique' => false,
),
) + parent::getOrderableColumns();
}
protected function getPagingValueMap($cursor, array $keys) {
$event = $this->loadCursorObject($cursor);
return array(
'start' => $event->getStartDateTimeEpoch(),
'id' => $event->getID(),
);
}
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;
}
protected function loadPage() {
$events = $this->loadStandardPage($this->newResultObject());
$viewer = $this->getViewer();
foreach ($events as $event) {
$event->applyViewerTimezone($viewer);
}
if (!$this->generateGhosts) {
return $events;
}
$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.'));
}
foreach ($events as $key => $event) {
$sequence_start = 0;
$sequence_end = null;
$end = null;
$instance_of = $event->getInstanceOfEventPHID();
if ($instance_of == null && $this->isCancelled !== null) {
if ($event->getIsCancelled() != $this->isCancelled) {
unset($events[$key]);
continue;
}
}
}
// 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;
}
}
// Now that we've picked out all the parent events, we can immediately
// discard anything outside of the time window.
$events = $this->getEventsInRange($events);
$generate_from = $this->rangeBegin;
$generate_until = $this->rangeEnd;
foreach ($parents as $key => $event) {
$duration = $event->getDuration();
$start_date = $this->getRecurrenceWindowStart(
$event,
$generate_from - $duration);
$end_date = $this->getRecurrenceWindowEnd(
$event,
$generate_until);
$limit = $this->getRecurrenceLimit($event, $raw_limit);
$set = $event->newRecurrenceSet();
$recurrences = $set->getEventsBetween(
null,
$end_date,
$limit + 1);
// 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;
}
foreach ($recurrences as $sequence_index => $sequence_datetime) {
if (!$sequence_index) {
// This is the parent event, which we already have.
continue;
}
if ($start_epoch) {
if ($sequence_datetime->getEpoch() < $start_epoch) {
continue;
}
}
$events[] = $event->newGhost(
$viewer,
$sequence_index,
$sequence_datetime);
}
// 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) {
$events = msort($events, 'getStartDateTimeEpoch');
$events = array_slice($events, 0, $raw_limit, true);
$generate_until = last($events)->getEndDateTimeEpoch();
}
}
}
// 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.
// 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()) {
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;
}
// 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;
}
}
$events = msort($events, 'getStartDateTimeEpoch');
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 buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'event.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'event.phid IN (%Ls)',
$this->phids);
}
// 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.
if ($this->rangeBegin) {
$where[] = qsprintf(
$conn,
'(event.utcUntilEpoch >= %d) OR (event.utcUntilEpoch IS NULL)',
$this->rangeBegin - phutil_units('16 hours in seconds'));
}
if ($this->rangeEnd) {
$where[] = qsprintf(
$conn,
'event.utcInitialEpoch <= %d',
$this->rangeEnd + phutil_units('16 hours in seconds'));
}
if ($this->utcInitialEpochMin !== null) {
$where[] = qsprintf(
$conn,
'event.utcInitialEpoch >= %d',
$this->utcInitialEpochMin);
}
if ($this->utcInitialEpochMax !== null) {
$where[] = qsprintf(
$conn,
'event.utcInitialEpoch <= %d',
$this->utcInitialEpochMax);
}
if ($this->inviteePHIDs !== null) {
$where[] = qsprintf(
$conn,
'invitee.inviteePHID IN (%Ls)',
$this->inviteePHIDs);
}
if ($this->hostPHIDs !== null) {
$where[] = qsprintf(
$conn,
'event.hostPHID IN (%Ls)',
$this->hostPHIDs);
}
if ($this->isCancelled !== null) {
$where[] = qsprintf(
$conn,
'event.isCancelled = %d',
(int)$this->isCancelled);
}
if ($this->eventsWithNoParent == true) {
$where[] = qsprintf(
$conn,
'event.instanceOfEventPHID IS NULL');
}
if ($this->instanceSequencePairs !== null) {
$sql = array();
foreach ($this->instanceSequencePairs as $pair) {
$sql[] = qsprintf(
$conn,
'(event.instanceOfEventPHID = %s AND event.sequenceIndex = %d)',
$pair[0],
$pair[1]);
}
$where[] = qsprintf(
$conn,
'%Q',
implode(' OR ', $sql));
}
if ($this->isStub !== null) {
$where[] = qsprintf(
$conn,
'event.isStub = %d',
(int)$this->isStub);
}
if ($this->parentEventPHIDs !== null) {
$where[] = qsprintf(
$conn,
'event.instanceOfEventPHID IN (%Ls)',
$this->parentEventPHIDs);
}
if ($this->importSourcePHIDs !== null) {
$where[] = qsprintf(
$conn,
'event.importSourcePHID IN (%Ls)',
$this->importSourcePHIDs);
}
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);
}
if ($this->isImported !== null) {
if ($this->isImported) {
$where[] = qsprintf(
$conn,
'event.importSourcePHID IS NOT NULL');
} else {
$where[] = qsprintf(
$conn,
'event.importSourcePHID IS NULL');
}
}
return $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) {
$instance_of_event_phids = array();
$recurring_events = array();
$viewer = $this->getViewer();
$events = $this->getEventsInRange($events);
$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();
$imports = mpull($imports, null, 'getPHID');
} else {
$imports = array();
}
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);
}
$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, 'getStartDateTimeEpoch');
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);
}
}
return $events;
}
private function getEventsInRange(array $events) {
$range_start = $this->rangeBegin;
$range_end = $this->rangeEnd;
foreach ($events as $key => $event) {
$event_start = $event->getStartDateTimeEpoch();
$event_end = $event->getEndDateTimeEpoch();
if ($range_start && $event_end < $range_start) {
unset($events[$key]);
}
if ($range_end && $event_start > $range_end) {
unset($events[$key]);
}
}
return $events;
}
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) {
$count = $event->getRecurrenceCount();
if ($count && ($count <= $raw_limit)) {
return ($count - 1);
}
return $raw_limit;
}
}
diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
index 704f161e3..05d4eadb1 100644
--- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
@@ -1,625 +1,631 @@
<?php
final class PhabricatorCalendarEventSearchEngine
extends PhabricatorApplicationSearchEngine {
private $calendarYear;
private $calendarMonth;
private $calendarDay;
public function getResultTypeDescription() {
return pht('Calendar Events');
}
public function getApplicationClassName() {
return 'PhabricatorCalendarApplication';
}
public function newQuery() {
$viewer = $this->requireViewer();
return id(new PhabricatorCalendarEventQuery())
->needRSVPs(array($viewer->getPHID()));
}
protected function shouldShowOrderField() {
return false;
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Hosts'))
->setKey('hostPHIDs')
->setAliases(array('host', 'hostPHID', 'hosts'))
->setDatasource(new PhabricatorPeopleUserFunctionDatasource()),
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Invited'))
->setKey('invitedPHIDs')
->setDatasource(new PhabricatorCalendarInviteeDatasource()),
id(new PhabricatorSearchDateControlField())
->setLabel(pht('Occurs After'))
->setKey('rangeStart'),
id(new PhabricatorSearchDateControlField())
->setLabel(pht('Occurs Before'))
->setKey('rangeEnd')
->setAliases(array('rangeEnd')),
id(new PhabricatorSearchCheckboxesField())
->setKey('upcoming')
->setOptions(array(
'upcoming' => pht('Show only upcoming events.'),
)),
id(new PhabricatorSearchSelectField())
->setLabel(pht('Cancelled Events'))
->setKey('isCancelled')
->setOptions($this->getCancelledOptions())
->setDefault('active'),
id(new PhabricatorPHIDsSearchField())
->setLabel(pht('Import Sources'))
->setKey('importSourcePHIDs')
->setAliases(array('importSourcePHID')),
id(new PhabricatorSearchSelectField())
->setLabel(pht('Display Options'))
->setKey('display')
->setOptions($this->getViewOptions())
->setDefault('month'),
);
}
private function getCancelledOptions() {
return array(
'active' => pht('Active Events Only'),
'cancelled' => pht('Cancelled Events Only'),
'both' => pht('Both Cancelled and Active Events'),
);
}
private function getViewOptions() {
return array(
'month' => pht('Month View'),
'day' => pht('Day View'),
'list' => pht('List View'),
);
}
+ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+ $query = parent::buildQueryFromSavedQuery($saved);
+
+ // If this is an export query for generating an ".ics" file, don't
+ // build ghost events.
+ if ($saved->getParameter('export')) {
+ $query->setGenerateGhosts(false);
+ }
+
+ return $query;
+ }
+
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
$viewer = $this->requireViewer();
if ($map['hostPHIDs']) {
$query->withHostPHIDs($map['hostPHIDs']);
}
if ($map['invitedPHIDs']) {
$query->withInvitedPHIDs($map['invitedPHIDs']);
}
$range_start = $map['rangeStart'];
$range_end = $map['rangeEnd'];
$display = $map['display'];
if ($map['upcoming'] && $map['upcoming'][0] == 'upcoming') {
$upcoming = true;
} else {
$upcoming = false;
}
list($range_start, $range_end) = $this->getQueryDateRange(
$range_start,
$range_end,
$display,
$upcoming);
$query->withDateRange($range_start, $range_end);
switch ($map['isCancelled']) {
case 'active':
$query->withIsCancelled(false);
break;
case 'cancelled':
$query->withIsCancelled(true);
break;
}
if ($map['importSourcePHIDs']) {
$query->withImportSourcePHIDs($map['importSourcePHIDs']);
}
- // Generate ghosts (and ignore stub events) if we aren't querying for
- // specific events or exporting.
- if (!empty($map['export'])) {
- // This is a specific mode enabled by event exports.
- $query
- ->withIsStub(false);
- } else if (!$map['ids'] && !$map['phids']) {
+ if (!$map['ids'] && !$map['phids']) {
$query
->withIsStub(false)
->setGenerateGhosts(true);
}
return $query;
}
private function getQueryDateRange(
$start_date_wild,
$end_date_wild,
$display,
$upcoming) {
$start_date_value = $this->getSafeDate($start_date_wild);
$end_date_value = $this->getSafeDate($end_date_wild);
$viewer = $this->requireViewer();
$timezone = new DateTimeZone($viewer->getTimezoneIdentifier());
$min_range = null;
$max_range = null;
$min_range = $start_date_value->getEpoch();
$max_range = $end_date_value->getEpoch();
if ($display == 'month' || $display == 'day') {
list($start_year, $start_month, $start_day) =
$this->getDisplayYearAndMonthAndDay($min_range, $max_range, $display);
$start_day = new DateTime(
"{$start_year}-{$start_month}-{$start_day}",
$timezone);
$next = clone $start_day;
if ($display == 'month') {
$next->modify('+1 month');
} else if ($display == 'day') {
$next->modify('+7 day');
}
$display_start = $start_day->format('U');
$display_end = $next->format('U');
$start_of_week = $viewer->getUserSetting(
PhabricatorWeekStartDaySetting::SETTINGKEY);
$end_of_week = ($start_of_week + 6) % 7;
$first_of_month = $start_day->format('w');
$last_of_month = id(clone $next)->modify('-1 day')->format('w');
if (!$min_range || ($min_range < $display_start)) {
$min_range = $display_start;
if ($display == 'month' &&
$first_of_month !== $start_of_week) {
$interim_day_num = ($first_of_month + 7 - $start_of_week) % 7;
$min_range = id(clone $start_day)
->modify('-'.$interim_day_num.' days')
->format('U');
}
}
if (!$max_range || ($max_range > $display_end)) {
$max_range = $display_end;
if ($display == 'month' &&
$last_of_month !== $end_of_week) {
$interim_day_num = ($end_of_week + 7 - $last_of_month) % 7;
$max_range = id(clone $next)
->modify('+'.$interim_day_num.' days')
->format('U');
}
}
}
if ($upcoming) {
$now = PhabricatorTime::getNow();
if ($min_range) {
$min_range = max($now, $min_range);
} else {
$min_range = $now;
}
}
return array($min_range, $max_range);
}
protected function getURI($path) {
return '/calendar/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array(
'month' => pht('Month View'),
'day' => pht('Day View'),
'upcoming' => pht('Upcoming Events'),
'all' => pht('All Events'),
);
return $names;
}
public function setCalendarYearAndMonthAndDay($year, $month, $day = null) {
$this->calendarYear = $year;
$this->calendarMonth = $month;
$this->calendarDay = $day;
return $this;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'month':
return $query->setParameter('display', 'month');
case 'day':
return $query->setParameter('display', 'day');
case 'upcoming':
return $query
->setParameter('display', 'list')
->setParameter('upcoming', array(
0 => 'upcoming',
));
case 'all':
return $query;
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $events,
PhabricatorSavedQuery $query,
array $handles) {
if ($this->isMonthView($query)) {
$result = $this->buildCalendarMonthView($events, $query);
} else if ($this->isDayView($query)) {
$result = $this->buildCalendarDayView($events, $query);
} else {
$result = $this->buildCalendarListView($events, $query);
}
return $result;
}
private function buildCalendarListView(
array $events,
PhabricatorSavedQuery $query) {
assert_instances_of($events, 'PhabricatorCalendarEvent');
$viewer = $this->requireViewer();
$list = new PHUIObjectItemListView();
foreach ($events as $event) {
if ($event->getIsGhostEvent()) {
$monogram = $event->getParentEvent()->getMonogram();
$index = $event->getSequenceIndex();
$monogram = "{$monogram}/{$index}";
} else {
$monogram = $event->getMonogram();
}
$item = id(new PHUIObjectItemView())
->setUser($viewer)
->setObject($event)
->setObjectName($monogram)
->setHeader($event->getName())
->setHref($event->getURI());
$item->addAttribute($event->renderEventDate($viewer, false));
if ($event->getIsCancelled()) {
$item->setDisabled(true);
}
$status_icon = $event->getDisplayIcon($viewer);
$status_color = $event->getDisplayIconColor($viewer);
$status_label = $event->getDisplayIconLabel($viewer);
$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);
$host = pht(
'Hosted by %s',
$viewer->renderHandle($event->getHostPHID()));
$item->addByline($host);
$list->addItem($item);
}
return $this->newResultView()
->setObjectList($list)
->setNoDataString(pht('No events found.'));
}
private function buildCalendarMonthView(
array $events,
PhabricatorSavedQuery $query) {
assert_instances_of($events, 'PhabricatorCalendarEvent');
$viewer = $this->requireViewer();
$now = PhabricatorTime::getNow();
list($start_year, $start_month) =
$this->getDisplayYearAndMonthAndDay(
$this->getQueryDateFrom($query)->getEpoch(),
$this->getQueryDateTo($query)->getEpoch(),
$query->getParameter('display'));
$now_year = phabricator_format_local_time($now, $viewer, 'Y');
$now_month = phabricator_format_local_time($now, $viewer, 'm');
$now_day = phabricator_format_local_time($now, $viewer, 'j');
if ($start_month == $now_month && $start_year == $now_year) {
$month_view = new PHUICalendarMonthView(
$this->getQueryDateFrom($query),
$this->getQueryDateTo($query),
$start_month,
$start_year,
$now_day);
} else {
$month_view = new PHUICalendarMonthView(
$this->getQueryDateFrom($query),
$this->getQueryDateTo($query),
$start_month,
$start_year);
}
$month_view->setUser($viewer);
foreach ($events as $event) {
$epoch_min = $event->getStartDateTimeEpoch();
$epoch_max = $event->getEndDateTimeEpoch();
$is_invited = $event->isRSVPInvited($viewer->getPHID());
$event_view = id(new AphrontCalendarEventView())
->setHostPHID($event->getHostPHID())
->setEpochRange($epoch_min, $epoch_max)
->setIsCancelled($event->getIsCancelled())
->setName($event->getName())
->setURI($event->getURI())
->setIsAllDay($event->getIsAllDay())
->setIcon($event->getDisplayIcon($viewer))
->setViewerIsInvited($is_invited)
->setDatetimeSummary($event->renderEventDate($viewer, true))
->setIconColor($event->getDisplayIconColor($viewer));
$month_view->addEvent($event_view);
}
$month_view->setBrowseURI(
$this->getURI('query/'.$query->getQueryKey().'/'));
$from = $this->getQueryDateFrom($query)->getDateTime();
$crumbs = array();
$crumbs[] = id(new PHUICrumbView())
->setName($from->format('F Y'));
$header = id(new PHUIHeaderView())
->setProfileHeader(true)
->setHeader($from->format('F Y'));
return $this->newResultView($month_view)
->setCrumbs($crumbs)
->setHeader($header);
}
private function buildCalendarDayView(
array $events,
PhabricatorSavedQuery $query) {
$viewer = $this->requireViewer();
list($start_year, $start_month, $start_day) =
$this->getDisplayYearAndMonthAndDay(
$this->getQueryDateFrom($query)->getEpoch(),
$this->getQueryDateTo($query)->getEpoch(),
$query->getParameter('display'));
$day_view = id(new PHUICalendarDayView(
$this->getQueryDateFrom($query),
$this->getQueryDateTo($query),
$start_year,
$start_month,
$start_day))
->setQuery($query->getQueryKey());
$day_view->setUser($viewer);
$phids = mpull($events, 'getHostPHID');
foreach ($events as $event) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$event,
PhabricatorPolicyCapability::CAN_EDIT);
$epoch_min = $event->getStartDateTimeEpoch();
$epoch_max = $event->getEndDateTimeEpoch();
$status_icon = $event->getDisplayIcon($viewer);
$status_color = $event->getDisplayIconColor($viewer);
$event_view = id(new AphrontCalendarEventView())
->setCanEdit($can_edit)
->setEventID($event->getID())
->setEpochRange($epoch_min, $epoch_max)
->setIsAllDay($event->getIsAllDay())
->setIcon($status_icon)
->setIconColor($status_color)
->setName($event->getName())
->setURI($event->getURI())
->setDatetimeSummary($event->renderEventDate($viewer, true))
->setIsCancelled($event->getIsCancelled());
$day_view->addEvent($event_view);
}
$browse_uri = $this->getURI('query/'.$query->getQueryKey().'/');
$day_view->setBrowseURI($browse_uri);
$from = $this->getQueryDateFrom($query)->getDateTime();
$month_uri = $browse_uri.$from->format('Y/m/');
$crumbs = array(
id(new PHUICrumbView())
->setName($from->format('F Y'))
->setHref($month_uri),
id(new PHUICrumbView())
->setName($from->format('D jS')),
);
$header = id(new PHUIHeaderView())
->setProfileHeader(true)
->setHeader($from->format('D, F jS'));
return $this->newResultView($day_view)
->setCrumbs($crumbs)
->setHeader($header);
}
private function getDisplayYearAndMonthAndDay(
$range_start,
$range_end,
$display) {
$viewer = $this->requireViewer();
$epoch = null;
if ($this->calendarYear && $this->calendarMonth) {
$start_year = $this->calendarYear;
$start_month = $this->calendarMonth;
$start_day = $this->calendarDay ? $this->calendarDay : 1;
} else {
if ($range_start) {
$epoch = $range_start;
} else if ($range_end) {
$epoch = $range_end;
} else {
$epoch = time();
}
if ($display == 'month') {
$day = 1;
} else {
$day = phabricator_format_local_time($epoch, $viewer, 'd');
}
$start_year = phabricator_format_local_time($epoch, $viewer, 'Y');
$start_month = phabricator_format_local_time($epoch, $viewer, 'm');
$start_day = $day;
}
return array($start_year, $start_month, $start_day);
}
public function getPageSize(PhabricatorSavedQuery $saved) {
if ($this->isMonthView($saved) || $this->isDayView($saved)) {
return $saved->getParameter('limit', 1000);
} else {
return $saved->getParameter('limit', 100);
}
}
private function getQueryDateFrom(PhabricatorSavedQuery $saved) {
if ($this->calendarYear && $this->calendarMonth) {
$viewer = $this->requireViewer();
$start_year = $this->calendarYear;
$start_month = $this->calendarMonth;
$start_day = $this->calendarDay ? $this->calendarDay : 1;
return AphrontFormDateControlValue::newFromDictionary(
$viewer,
array(
'd' => "{$start_year}-{$start_month}-{$start_day}",
));
}
return $this->getQueryDate($saved, 'rangeStart');
}
private function getQueryDateTo(PhabricatorSavedQuery $saved) {
return $this->getQueryDate($saved, 'rangeEnd');
}
private function getQueryDate(PhabricatorSavedQuery $saved, $key) {
$viewer = $this->requireViewer();
$wild = $saved->getParameter($key);
return $this->getSafeDate($wild);
}
private function getSafeDate($value) {
$viewer = $this->requireViewer();
if ($value) {
// ideally this would be consistent and always pass in the same type
if ($value instanceof AphrontFormDateControlValue) {
return $value;
} else {
$value = AphrontFormDateControlValue::newFromWild($viewer, $value);
}
} else {
$value = AphrontFormDateControlValue::newFromEpoch(
$viewer,
PhabricatorTime::getTodayMidnightDateTime($viewer)->format('U'));
$value->setEnabled(false);
}
$value->setOptional(true);
return $value;
}
private function isMonthView(PhabricatorSavedQuery $query) {
if ($this->isDayView($query)) {
return false;
}
if ($query->getParameter('display') == 'month') {
return true;
}
}
private function isDayView(PhabricatorSavedQuery $query) {
if ($query->getParameter('display') == 'day') {
return true;
}
if ($this->calendarDay) {
return true;
}
return false;
}
public function newUseResultsActions(PhabricatorSavedQuery $saved) {
$viewer = $this->requireViewer();
$can_export = $viewer->isLoggedIn();
return array(
id(new PhabricatorActionView())
->setIcon('fa-download')
->setName(pht('Export Query as .ics'))
->setDisabled(!$can_export)
->setHref('/calendar/export/edit/?queryKey='.$saved->getQueryKey()),
);
}
private function newResultView($content = null) {
// If we aren't rendering a dashboard panel, activate global drag-and-drop
// so you can import ".ics" files by dropping them directly onto the
// calendar.
if (!$this->isPanelContext()) {
$drop_upload = id(new PhabricatorGlobalUploadTargetView())
->setViewer($this->requireViewer())
->setHintText("\xE2\x87\xAA ".pht('Drop .ics Files to Import'))
->setSubmitURI('/calendar/import/drop/')
->setViewPolicy(PhabricatorPolicies::POLICY_NOONE);
$content = array(
$drop_upload,
$content,
);
}
return id(new PhabricatorApplicationSearchResultView())
->setContent($content);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Feb 26, 6:57 PM (1 d, 18 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
46/1f/ca61d80e86c49091f952aa47d1a7
Attached To
rPHAB Phabricator
Event Timeline
Log In to Comment