Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14725802
D16679.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D16679.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D16679: Export recurring events and build ICS files for configured exports
Attached
Detach File
Event Timeline
Log In to Comment