Page MenuHomePhabricator

D16679.diff
No OneTemporary

D16679.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2080,6 +2080,7 @@
'PhabricatorCalendarExportEditController' => 'applications/calendar/controller/PhabricatorCalendarExportEditController.php',
'PhabricatorCalendarExportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarExportEditEngine.php',
'PhabricatorCalendarExportEditor' => 'applications/calendar/editor/PhabricatorCalendarExportEditor.php',
+ 'PhabricatorCalendarExportICSController' => 'applications/calendar/controller/PhabricatorCalendarExportICSController.php',
'PhabricatorCalendarExportListController' => 'applications/calendar/controller/PhabricatorCalendarExportListController.php',
'PhabricatorCalendarExportModeTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportModeTransaction.php',
'PhabricatorCalendarExportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarExportNameTransaction.php',
@@ -6851,6 +6852,7 @@
'PhabricatorCalendarExportEditController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExportEditEngine' => 'PhabricatorEditEngine',
'PhabricatorCalendarExportEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'PhabricatorCalendarExportICSController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExportListController' => 'PhabricatorCalendarController',
'PhabricatorCalendarExportModeTransaction' => 'PhabricatorCalendarExportTransactionType',
'PhabricatorCalendarExportNameTransaction' => 'PhabricatorCalendarExportTransactionType',
diff --git a/src/applications/calendar/controller/PhabricatorCalendarController.php b/src/applications/calendar/controller/PhabricatorCalendarController.php
--- a/src/applications/calendar/controller/PhabricatorCalendarController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarController.php
@@ -2,4 +2,43 @@
abstract class PhabricatorCalendarController extends PhabricatorController {
+ protected function newICSResponse(
+ PhabricatorUser $viewer,
+ $file_name,
+ array $events) {
+ $events = mpull($events, null, 'getPHID');
+
+ if ($events) {
+ $child_map = id(new PhabricatorCalendarEventQuery())
+ ->setViewer($viewer)
+ ->withParentEventPHIDs(array_keys($events))
+ ->execute();
+ $child_map = mpull($child_map, null, 'getPHID');
+ } else {
+ $child_map = array();
+ }
+
+ $all_events = $events + $child_map;
+ $child_groups = mgroup($child_map, 'getInstanceOfEventPHID');
+
+ $document_node = new PhutilCalendarDocumentNode();
+
+ foreach ($all_events as $event) {
+ $child_events = idx($child_groups, $event->getPHID(), array());
+ $event_node = $event->newIntermediateEventNode($viewer, $child_events);
+ $document_node->appendChild($event_node);
+ }
+
+ $root_node = id(new PhutilCalendarRootNode())
+ ->appendChild($document_node);
+
+ $ics_data = id(new PhutilICSWriter())
+ ->writeICSDocument($root_node);
+
+ return id(new AphrontFileResponse())
+ ->setDownload($file_name)
+ ->setMimeType('text/calendar')
+ ->setContent($ics_data);
+ }
+
}
diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventExportController.php b/src/applications/calendar/controller/PhabricatorCalendarEventExportController.php
--- a/src/applications/calendar/controller/PhabricatorCalendarEventExportController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarEventExportController.php
@@ -19,22 +19,16 @@
return new Aphront404Response();
}
- $file_name = $event->getICSFilename();
- $event_node = $event->newIntermediateEventNode($viewer);
-
- $document_node = id(new PhutilCalendarDocumentNode())
- ->appendChild($event_node);
-
- $root_node = id(new PhutilCalendarRootNode())
- ->appendChild($document_node);
-
- $ics_data = id(new PhutilICSWriter())
- ->writeICSDocument($root_node);
+ if ($event->isChildEvent()) {
+ $target = $event->getParentEvent();
+ } else {
+ $target = $event;
+ }
- return id(new AphrontFileResponse())
- ->setDownload($file_name)
- ->setMimeType('text/calendar')
- ->setContent($ics_data);
+ return $this->newICSResponse(
+ $viewer,
+ $target->getICSFileName(),
+ array($target));
}
}
diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php
--- a/src/applications/calendar/controller/PhabricatorCalendarEventListController.php
+++ b/src/applications/calendar/controller/PhabricatorCalendarEventListController.php
@@ -11,17 +11,19 @@
$year = $request->getURIData('year');
$month = $request->getURIData('month');
$day = $request->getURIData('day');
+
$engine = new PhabricatorCalendarEventSearchEngine();
if ($month && $year) {
$engine->setCalendarYearAndMonthAndDay($year, $month, $day);
}
- $controller = id(new PhabricatorApplicationSearchController())
- ->setQueryKey($request->getURIData('queryKey'))
- ->setSearchEngine($engine);
+ $nav_items = $this->buildNavigationItems();
- return $this->delegateToController($controller);
+ return $engine
+ ->setNavigationItems($nav_items)
+ ->setController($this)
+ ->buildResponse();
}
protected function buildApplicationCrumbs() {
@@ -34,4 +36,18 @@
return $crumbs;
}
+ protected function buildNavigationItems() {
+ $items = array();
+
+ $items[] = id(new PHUIListItemView())
+ ->setType(PHUIListItemView::TYPE_LABEL)
+ ->setName(pht('Import/Export'));
+
+ $items[] = id(new PHUIListItemView())
+ ->setName('Exports')
+ ->setHref('/calendar/export/');
+
+ return $items;
+ }
+
}
diff --git a/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php b/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/calendar/controller/PhabricatorCalendarExportICSController.php
@@ -0,0 +1,93 @@
+<?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();
+ }
+
+ $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);
+
+ $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
--- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php
@@ -13,6 +13,7 @@
private $eventsWithNoParent;
private $instanceSequencePairs;
private $isStub;
+ private $parentEventPHIDs;
private $generateGhosts = false;
@@ -71,6 +72,11 @@
return $this;
}
+ public function withParentEventPHIDs(array $parent_phids) {
+ $this->parentEventPHIDs = $parent_phids;
+ return $this;
+ }
+
protected function getDefaultOrderVector() {
return array('start', 'id');
}
@@ -315,14 +321,14 @@
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
- if ($this->ids) {
+ if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'event.id IN (%Ld)',
$this->ids);
}
- if ($this->phids) {
+ if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'event.phid IN (%Ls)',
@@ -354,7 +360,7 @@
$this->inviteePHIDs);
}
- if ($this->hostPHIDs) {
+ if ($this->hostPHIDs !== null) {
$where[] = qsprintf(
$conn,
'event.hostPHID IN (%Ls)',
@@ -398,6 +404,13 @@
(int)$this->isStub);
}
+ if ($this->parentEventPHIDs !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'event.instanceOfEventPHID IN (%Ls)',
+ $this->parentEventPHIDs);
+ }
+
return $where;
}
diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
--- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
+++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php
@@ -115,8 +115,12 @@
}
// Generate ghosts (and ignore stub events) if we aren't querying for
- // specific events.
- if (!$map['ids'] && !$map['phids']) {
+ // 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']) {
$query
->withIsStub(false)
->setGenerateGhosts(true);
diff --git a/src/applications/calendar/query/PhabricatorCalendarExportQuery.php b/src/applications/calendar/query/PhabricatorCalendarExportQuery.php
--- a/src/applications/calendar/query/PhabricatorCalendarExportQuery.php
+++ b/src/applications/calendar/query/PhabricatorCalendarExportQuery.php
@@ -6,6 +6,7 @@
private $ids;
private $phids;
private $authorPHIDs;
+ private $secretKeys;
private $isDisabled;
public function withIDs(array $ids) {
@@ -28,6 +29,11 @@
return $this;
}
+ public function withSecretKeys(array $keys) {
+ $this->secretKeys = $keys;
+ return $this;
+ }
+
public function newResultObject() {
return new PhabricatorCalendarExport();
}
@@ -67,6 +73,13 @@
(int)$this->isDisabled);
}
+ if ($this->secretKeys !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'export.secretKey IN (%Ls)',
+ $this->secretKeys);
+ }
+
return $where;
}
diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
--- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php
+++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php
@@ -601,11 +601,28 @@
return $this->getMonogram().'.ics';
}
- public function newIntermediateEventNode(PhabricatorUser $viewer) {
+ public function newIntermediateEventNode(
+ PhabricatorUser $viewer,
+ array $children) {
+
$base_uri = new PhutilURI(PhabricatorEnv::getProductionURI('/'));
$domain = $base_uri->getDomain();
- $uid = $this->getPHID().'@'.$domain;
+ // NOTE: For recurring events, all of the events in the series have the
+ // same UID (the UID of the parent). The child event instances are
+ // differentiated by the "RECURRENCE-ID" field.
+ if ($this->isChildEvent()) {
+ $parent = $this->getParentEvent();
+ $instance_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch(
+ $this->getUTCInstanceEpoch());
+ $recurrence_id = $instance_datetime->getISO8601();
+ $rrule = null;
+ } else {
+ $parent = $this;
+ $recurrence_id = null;
+ $rrule = $this->newRecurrenceRule();
+ }
+ $uid = $parent->getPHID().'@'.$domain;
$created = $this->getDateCreated();
$created = PhutilCalendarAbsoluteDateTime::newFromEpoch($created);
@@ -674,6 +691,8 @@
->setStatus($status);
}
+ // TODO: Use $children to generate EXDATE/RDATE information.
+
$node = id(new PhutilCalendarEventNode())
->setUID($uid)
->setName($this->getName())
@@ -685,6 +704,14 @@
->setOrganizer($organizer)
->setAttendees($attendees);
+ if ($rrule) {
+ $node->setRecurrenceRule($rrule);
+ }
+
+ if ($recurrence_id) {
+ $node->setRecurrenceID($recurrence_id);
+ }
+
return $node;
}
@@ -833,6 +860,11 @@
$start = $this->newStartDateTime();
$rrule->setStartDateTime($start);
+ $until = $this->newUntilDateTime();
+ if ($until) {
+ $rrule->setUntil($until);
+ }
+
return $rrule;
}

File Metadata

Mime Type
text/plain
Expires
Sun, Jan 19, 2:44 AM (20 h, 46 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7015965
Default Alt Text
D16679.diff (14 KB)

Event Timeline