diff --git a/resources/sql/autopatches/20161012.cal.01.import.sql b/resources/sql/autopatches/20161012.cal.01.import.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20161012.cal.01.import.sql @@ -0,0 +1,15 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_import ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + name LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + authorPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + engineType VARCHAR(64) NOT NULL, + parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + isDisabled BOOL NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_author` (authorPHID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20161012.cal.02.importxaction.sql b/resources/sql/autopatches/20161012.cal.02.importxaction.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20161012.cal.02.importxaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_calendar.calendar_importtransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20161012.cal.03.eventimport.sql b/resources/sql/autopatches/20161012.cal.03.eventimport.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20161012.cal.03.eventimport.sql @@ -0,0 +1,11 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD importAuthorPHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD importSourcePHID VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD importUIDIndex BINARY(12); + +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD importUID LONGTEXT COLLATE {$COLLATE_TEXT}; 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 @@ -2100,6 +2100,14 @@ 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 'PhabricatorCalendarICSWriter' => 'applications/calendar/util/PhabricatorCalendarICSWriter.php', 'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php', + 'PhabricatorCalendarImport' => 'applications/calendar/storage/PhabricatorCalendarImport.php', + 'PhabricatorCalendarImportEditor' => 'applications/calendar/editor/PhabricatorCalendarImportEditor.php', + 'PhabricatorCalendarImportListController' => 'applications/calendar/controller/PhabricatorCalendarImportListController.php', + 'PhabricatorCalendarImportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarImportPHIDType.php', + 'PhabricatorCalendarImportQuery' => 'applications/calendar/query/PhabricatorCalendarImportQuery.php', + 'PhabricatorCalendarImportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportSearchEngine.php', + 'PhabricatorCalendarImportTransaction' => 'applications/calendar/storage/PhabricatorCalendarImportTransaction.php', + 'PhabricatorCalendarImportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php', 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', 'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php', 'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php', @@ -6789,6 +6797,7 @@ 'PhabricatorCalendarEvent' => array( 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', 'PhabricatorProjectInterface', 'PhabricatorMarkupInterface', 'PhabricatorApplicationTransactionInterface', @@ -6879,6 +6888,19 @@ 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarICSWriter' => 'Phobject', 'PhabricatorCalendarIconSet' => 'PhabricatorIconSet', + 'PhabricatorCalendarImport' => array( + 'PhabricatorCalendarDAO', + 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorDestructibleInterface', + ), + 'PhabricatorCalendarImportEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorCalendarImportListController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarImportPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorCalendarImportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorCalendarImportSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorCalendarImportTransaction' => 'PhabricatorModularTransaction', + 'PhabricatorCalendarImportTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec', diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -73,7 +73,16 @@ => 'PhabricatorCalendarExportICSController', 'disable/(?P[1-9]\d*)/' => 'PhabricatorCalendarExportDisableController', - + ), + 'import/' => array( + $this->getQueryRoutePattern() + => 'PhabricatorCalendarImportListController', + $this->getEditRoutePattern('edit/') + => 'PhabricatorCalendarImportEditController', + '(?P[1-9]\d*)/' + => 'PhabricatorCalendarImportViewController', + 'disable/(?P[1-9]\d*)/' + => 'PhabricatorCalendarImportDisableController', ), ), ); 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 @@ -44,6 +44,10 @@ ->setName(pht('Import/Export')); $items[] = id(new PHUIListItemView()) + ->setName('Imports') + ->setHref('/calendar/import/'); + + $items[] = id(new PHUIListItemView()) ->setName('Exports') ->setHref('/calendar/export/'); diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportListController.php b/src/applications/calendar/controller/PhabricatorCalendarImportListController.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportListController.php @@ -0,0 +1,12 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php b/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php @@ -0,0 +1,18 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $import = $objects[$phid]; + + $id = $import->getID(); + $name = $import->getName(); + $uri = $import->getURI(); + + $handle + ->setName($name) + ->setFullName(pht('Calendar Import %s: %s', $id, $name)) + ->setURI($uri); + + if ($import->getIsDisabled()) { + $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); + } + } + } + +} 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 @@ -14,6 +14,7 @@ private $instanceSequencePairs; private $isStub; private $parentEventPHIDs; + private $importSourcePHIDs; private $generateGhosts = false; @@ -77,6 +78,11 @@ return $this; } + public function withImportSourcePHIDs(array $import_phids) { + $this->importSourcePHIDs = $import_phids; + return $this; + } + protected function getDefaultOrderVector() { return array('start', 'id'); } @@ -411,6 +417,13 @@ $this->parentEventPHIDs); } + if ($this->importSourcePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'event.importSourcePHID IN (%Ls)', + $this->importSourcePHIDs); + } + return $where; } @@ -441,6 +454,42 @@ $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(); + $sources = mpull($sources, null, 'getPHID'); + } else { + $sources = 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) { @@ -561,5 +610,4 @@ return $raw_limit; } - } diff --git a/src/applications/calendar/query/PhabricatorCalendarImportQuery.php b/src/applications/calendar/query/PhabricatorCalendarImportQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarImportQuery.php @@ -0,0 +1,81 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withAuthorPHIDs(array $phids) { + $this->authorPHIDs = $phids; + return $this; + } + + public function withIsDisabled($is_disabled) { + $this->isDisabled = $is_disabled; + return $this; + } + + public function newResultObject() { + return new PhabricatorCalendarImport(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'import.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'import.phid IN (%Ls)', + $this->phids); + } + + if ($this->authorPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'import.authorPHID IN (%Ls)', + $this->authorPHIDs); + } + + if ($this->isDisabled !== null) { + $where[] = qsprintf( + $conn, + 'import.isDisabled = %d', + (int)$this->isDisabled); + } + + return $where; + } + + protected function getPrimaryTableAlias() { + return 'import'; + } + + public function getQueryApplicationClass() { + return 'PhabricatorCalendarApplication'; + } + +} diff --git a/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php @@ -0,0 +1,81 @@ +newQuery(); + + return $query; + } + + protected function getURI($path) { + return '/calendar/import/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Imports'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $imports, + PhabricatorSavedQuery $query, + array $handles) { + + assert_instances_of($imports, 'PhabricatorCalendarImport'); + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + foreach ($imports as $import) { + $item = id(new PHUIObjectItemView()) + ->setViewer($viewer) + ->setObjectName(pht('Import %d', $import->getID())) + ->setHeader($import->getName()) + ->setHref($import->getURI()); + + if ($import->getIsDisabled()) { + $item->setDisabled(true); + } + + $list->addItem($item); + } + + $result = new PhabricatorApplicationSearchResultView(); + $result->setObjectList($list); + $result->setNoDataString(pht('No imports found.')); + + return $result; + } +} diff --git a/src/applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php b/src/applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php @@ -0,0 +1,10 @@ +setAllDayDateTo(0) ->setStartDateTime($datetime_start) ->setEndDateTime($datetime_end) + ->attachImportSource(null) ->applyViewerTimezone($actor); } @@ -306,6 +314,14 @@ $this->mailKey = Filesystem::readRandomCharacters(20); } + $import_uid = $this->getImportUID(); + if ($import_uid !== null) { + $index = PhabricatorHash::digestForIndex($import_uid); + } else { + $index = null; + } + $this->setImportUIDIndex($index); + $this->updateUTCEpochs(); return parent::save(); @@ -344,6 +360,11 @@ 'utcUntilEpoch' => 'epoch?', 'utcInstanceEpoch' => 'epoch?', + 'importAuthorPHID' => 'phid?', + 'importSourcePHID' => 'phid?', + 'importUIDIndex' => 'bytes12?', + 'importUID' => 'text?', + // TODO: DEPRECATED. 'allDayDateFrom' => 'epoch', 'allDayDateTo' => 'epoch', @@ -885,6 +906,17 @@ return $set; } + public function getImportSource() { + return $this->assertAttached($this->importSource); + } + + public function attachImportSource( + PhabricatorCalendarImport $import = null) { + $this->importSource = $import; + return $this; + } + + /* -( Markup Interface )--------------------------------------------------- */ @@ -947,11 +979,19 @@ case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: - return $this->getEditPolicy(); + if ($this->getImportSource()) { + return PhabricatorPolicy::POLICY_NOONE; + } else { + return $this->getEditPolicy(); + } } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + if ($this->getImportSource()) { + return false; + } + // The host of an event can always view and edit it. $user_phid = $this->getHostPHID(); if ($user_phid) { @@ -974,12 +1014,40 @@ } public function describeAutomaticCapability($capability) { + if ($this->getImportSource()) { + return pht( + 'Events imported from external sources can not be edited in '. + 'Phabricator.'); + } + return pht( 'The host of an event can always view and edit it. Users who are '. 'invited to an event can always view it.'); } +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + $extended = array(); + + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + $import_source = $this->getImportSource(); + if ($import_source) { + $extended[] = array( + $import_source, + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + break; + } + + return $extended; + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/calendar/storage/PhabricatorCalendarImport.php b/src/applications/calendar/storage/PhabricatorCalendarImport.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarImport.php @@ -0,0 +1,126 @@ +setAuthorPHID($actor->getPHID()) + ->setViewPolicy($actor->getPHID()) + ->setEditPolicy($actor->getPHID()) + ->setIsDisabled(0); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'parameters' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'name' => 'text', + 'engineType' => 'text64', + 'isDisabled' => 'bool', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_author' => array( + 'columns' => array('authorPHID'), + ), + ), + ) + parent::getConfiguration(); + } + + public function getPHIDType() { + return PhabricatorCalendarImportPHIDType::TYPECONST; + } + + public function getURI() { + $id = $this->getID(); + return "/calendar/import/{$id}/"; + } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorCalendarImportEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorCalendarImportTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $viewer = $engine->getViewer(); + + $this->openTransaction(); + + $events = id(new PhabricatorCalendarEventQuery()) + ->withImportSourcePHIDs(array($this->getPHID())) + ->execute(); + foreach ($events as $event) { + $engine->destroyObject($event); + } + + $this->delete(); + $this->saveTransaction(); + } + +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarImportTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarImportTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarImportTransaction.php @@ -0,0 +1,18 @@ +getAuthorPHID()); } -/* -( PhabricatorDestructableInterface )----------------------------------- */ +/* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) {