From 314dc30017d4177744dd9a6828ae089b955c3147 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 19 Oct 2016 09:29:10 -0700 Subject: [PATCH] Add a URI-based ICS import source engine Summary: Ref T10747. This doesn't have a "keep up to date" option yet, but can, e.g., fetch a Google Calendar URI Test Plan: Fetched a Google Calendar URI, got some events imported. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16730 --- src/__phutil_library_map__.php | 8 ++ ...PhabricatorCalendarICSFileImportEngine.php | 102 ++++++++++++++++ .../PhabricatorCalendarICSImportEngine.php | 95 +-------------- .../PhabricatorCalendarICSURIImportEngine.php | 111 ++++++++++++++++++ .../PhabricatorCalendarImportFetchLogType.php | 33 ++++++ .../PhabricatorCalendarImportICSLogType.php | 2 +- ...icatorCalendarImportICSFileTransaction.php | 2 +- ...ricatorCalendarImportICSURITransaction.php | 73 ++++++++++++ 8 files changed, 332 insertions(+), 94 deletions(-) create mode 100644 src/applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php create mode 100644 src/applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php create mode 100644 src/applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php create mode 100644 src/applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d225434c9c..e859dcdce9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2100,7 +2100,9 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', + 'PhabricatorCalendarICSFileImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php', 'PhabricatorCalendarICSImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSImportEngine.php', + 'PhabricatorCalendarICSURIImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php', 'PhabricatorCalendarICSWriter' => 'applications/calendar/util/PhabricatorCalendarICSWriter.php', 'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php', 'PhabricatorCalendarImport' => 'applications/calendar/storage/PhabricatorCalendarImport.php', @@ -2117,9 +2119,11 @@ phutil_register_library_map(array( 'PhabricatorCalendarImportEmptyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEmptyLogType.php', 'PhabricatorCalendarImportEngine' => 'applications/calendar/import/PhabricatorCalendarImportEngine.php', 'PhabricatorCalendarImportEpochLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportEpochLogType.php', + 'PhabricatorCalendarImportFetchLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php', 'PhabricatorCalendarImportFrequencyLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportFrequencyLogType.php', 'PhabricatorCalendarImportICSFileTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php', 'PhabricatorCalendarImportICSLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php', + 'PhabricatorCalendarImportICSURITransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php', 'PhabricatorCalendarImportIgnoredNodeLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportIgnoredNodeLogType.php', 'PhabricatorCalendarImportListController' => 'applications/calendar/controller/PhabricatorCalendarImportListController.php', 'PhabricatorCalendarImportLog' => 'applications/calendar/storage/PhabricatorCalendarImportLog.php', @@ -6931,7 +6935,9 @@ phutil_register_library_map(array( 'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', + 'PhabricatorCalendarICSFileImportEngine' => 'PhabricatorCalendarICSImportEngine', 'PhabricatorCalendarICSImportEngine' => 'PhabricatorCalendarImportEngine', + 'PhabricatorCalendarICSURIImportEngine' => 'PhabricatorCalendarICSImportEngine', 'PhabricatorCalendarICSWriter' => 'Phobject', 'PhabricatorCalendarIconSet' => 'PhabricatorIconSet', 'PhabricatorCalendarImport' => array( @@ -6953,9 +6959,11 @@ phutil_register_library_map(array( 'PhabricatorCalendarImportEmptyLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportEngine' => 'Phobject', 'PhabricatorCalendarImportEpochLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportFetchLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportFrequencyLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportICSFileTransaction' => 'PhabricatorCalendarImportTransactionType', 'PhabricatorCalendarImportICSLogType' => 'PhabricatorCalendarImportLogType', + 'PhabricatorCalendarImportICSURITransaction' => 'PhabricatorCalendarImportTransactionType', 'PhabricatorCalendarImportIgnoredNodeLogType' => 'PhabricatorCalendarImportLogType', 'PhabricatorCalendarImportListController' => 'PhabricatorCalendarController', 'PhabricatorCalendarImportLog' => array( diff --git a/src/applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php new file mode 100644 index 0000000000..7284776da6 --- /dev/null +++ b/src/applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php @@ -0,0 +1,102 @@ +getParameter($phid_key); + + $properties->addProperty( + pht('Source File'), + $viewer->renderHandle($file_phid)); + } + + public function newEditEngineFields( + PhabricatorEditEngine $engine, + PhabricatorCalendarImport $import) { + $fields = array(); + + if ($engine->getIsCreate()) { + $fields[] = id(new PhabricatorFileEditField()) + ->setKey('icsFilePHID') + ->setLabel(pht('ICS File')) + ->setDescription(pht('ICS file to import.')) + ->setTransactionType( + PhabricatorCalendarImportICSFileTransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('File PHID to import.')) + ->setConduitTypeDescription(pht('File PHID.')); + } + + return $fields; + } + + public function getDisplayName(PhabricatorCalendarImport $import) { + $filename_key = PhabricatorCalendarImportICSFileTransaction::PARAMKEY_NAME; + $filename = $import->getParameter($filename_key); + if (strlen($filename)) { + return pht('ICS File "%s"', $filename); + } else { + return pht('ICS File'); + } + } + + public function didCreateImport( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + + $phid_key = PhabricatorCalendarImportICSFileTransaction::PARAMKEY_FILE; + $file_phid = $import->getParameter($phid_key); + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->executeOne(); + if (!$file) { + throw new Exception( + pht( + 'Unable to load file ("%s") for import.', + $file_phid)); + } + + $data = $file->loadFileData(); + + return $this->importICSData($viewer, $import, $data); + } + + + public function canDisable( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + return false; + } + + public function explainCanDisable( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + return pht( + 'You can not disable import of an ICS file because the entire import '. + 'occurs immediately when you upload the file. There is no further '. + 'activity to disable.'); + } + + +} diff --git a/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php index ff98666cbe..3d94085469 100644 --- a/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php +++ b/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php @@ -1,84 +1,12 @@ getParameter($phid_key); - - $properties->addProperty( - pht('Source File'), - $viewer->renderHandle($file_phid)); - } - - public function newEditEngineFields( - PhabricatorEditEngine $engine, - PhabricatorCalendarImport $import) { - $fields = array(); - - if ($engine->getIsCreate()) { - $fields[] = id(new PhabricatorFileEditField()) - ->setKey('icsFilePHID') - ->setLabel(pht('ICS File')) - ->setDescription(pht('ICS file to import.')) - ->setTransactionType( - PhabricatorCalendarImportICSFileTransaction::TRANSACTIONTYPE) - ->setConduitDescription(pht('File PHID to import.')) - ->setConduitTypeDescription(pht('File PHID.')); - } - - return $fields; - } - - public function getDisplayName(PhabricatorCalendarImport $import) { - $filename_key = PhabricatorCalendarImportICSFileTransaction::PARAMKEY_NAME; - $filename = $import->getParameter($filename_key); - if (strlen($filename)) { - return pht('ICS File "%s"', $filename); - } else { - return pht('ICS File'); - } - } - - public function didCreateImport( - PhabricatorUser $viewer, - PhabricatorCalendarImport $import) { - - $phid_key = PhabricatorCalendarImportICSFileTransaction::PARAMKEY_FILE; - $file_phid = $import->getParameter($phid_key); - - $file = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($file_phid)) - ->executeOne(); - if (!$file) { - throw new Exception( - pht( - 'Unable to load file ("%s") for import.', - $file_phid)); - } - - $data = $file->loadFileData(); + $data) { $parser = new PhutilICSParser(); @@ -103,21 +31,4 @@ final class PhabricatorCalendarICSImportEngine return $this->importEventDocument($viewer, $import, $document); } - - public function canDisable( - PhabricatorUser $viewer, - PhabricatorCalendarImport $import) { - return false; - } - - public function explainCanDisable( - PhabricatorUser $viewer, - PhabricatorCalendarImport $import) { - return pht( - 'You can not disable import of an ICS file because the entire import '. - 'occurs immediately when you upload the file. There is no further '. - 'activity to disable.'); - } - - } diff --git a/src/applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php new file mode 100644 index 0000000000..7b1754a911 --- /dev/null +++ b/src/applications/calendar/import/PhabricatorCalendarICSURIImportEngine.php @@ -0,0 +1,111 @@ +getParameter($uri_key); + + // Since the URI may contain a secret hash, don't show it to users who + // can not edit the import. + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $import, + PhabricatorPolicyCapability::CAN_EDIT); + if (!$can_edit) { + $uri_display = phutil_tag('em', array(), pht('Restricted')); + } else if (!PhabricatorEnv::isValidRemoteURIForLink($uri)) { + $uri_display = $uri; + } else { + $uri_display = phutil_tag( + 'a', + array( + 'href' => $uri, + 'target' => '_blank', + ), + $uri); + } + + $properties->addProperty(pht('Source URI'), $uri_display); + } + + public function newEditEngineFields( + PhabricatorEditEngine $engine, + PhabricatorCalendarImport $import) { + $fields = array(); + + if ($engine->getIsCreate()) { + $fields[] = id(new PhabricatorTextEditField()) + ->setKey('uri') + ->setLabel(pht('URI')) + ->setDescription(pht('URI to import.')) + ->setTransactionType( + PhabricatorCalendarImportICSURITransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('URI to import.')) + ->setConduitTypeDescription(pht('New URI.')); + } + + return $fields; + } + + public function getDisplayName(PhabricatorCalendarImport $import) { + return pht('ICS URI'); + } + + public function didCreateImport( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + + $uri_key = PhabricatorCalendarImportICSURITransaction::PARAMKEY_URI; + $uri = $import->getParameter($uri_key); + + PhabricatorSystemActionEngine::willTakeAction( + array($viewer->getPHID()), + new PhabricatorFilesOutboundRequestAction(), + 1); + + $file = PhabricatorFile::newFromFileDownload( + $uri, + array( + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + 'authorPHID' => $import->getAuthorPHID(), + 'canCDN' => true, + )); + + $import->newLogMessage( + PhabricatorCalendarImportFetchLogType::LOGTYPE, + array( + 'file.phid' => $file->getPHID(), + )); + + $data = $file->loadFileData(); + + return $this->importICSData($viewer, $import, $data); + } + + public function canDisable( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import) { + return true; + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php new file mode 100644 index 0000000000..2a674239d6 --- /dev/null +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportFetchLogType.php @@ -0,0 +1,33 @@ +renderHandle($log->getParameter('file.phid')); + } + + public function getDisplayIcon( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'fa-download'; + } + + public function getDisplayColor( + PhabricatorUser $viewer, + PhabricatorCalendarImportLog $log) { + return 'green'; + } + +} diff --git a/src/applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php b/src/applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php index 30994b08ad..b3032f073e 100644 --- a/src/applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php +++ b/src/applications/calendar/importlog/PhabricatorCalendarImportICSLogType.php @@ -15,7 +15,7 @@ final class PhabricatorCalendarImportICSLogType PhabricatorUser $viewer, PhabricatorCalendarImportLog $log) { return pht( - 'Failed to parse ICS file ("%s"): %s', + 'Failed to parse ICS data ("%s"): %s', $log->getParameter('ics.code'), $log->getParameter('ics.message')); } diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php index 59cd91053f..26a968d13d 100644 --- a/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php @@ -34,7 +34,7 @@ final class PhabricatorCalendarImportICSFileTransaction $viewer = $this->getActor(); $errors = array(); - $ics_type = PhabricatorCalendarICSImportEngine::ENGINETYPE; + $ics_type = PhabricatorCalendarICSFileImportEngine::ENGINETYPE; $import_type = $object->getEngine()->getImportEngineType(); if ($import_type != $ics_type) { if (!$xactions) { diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php new file mode 100644 index 0000000000..13fdbd232b --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportICSURITransaction.php @@ -0,0 +1,73 @@ +getParameter(self::PARAMKEY_URI); + } + + public function applyInternalEffects($object, $value) { + $object->setParameter(self::PARAMKEY_URI, $value); + } + + public function getTitle() { + // NOTE: This transaction intentionally does not disclose the actual + // URI. + return pht( + '%s updated the import URI.', + $this->renderAuthor()); + } + + public function validateTransactions($object, array $xactions) { + $viewer = $this->getActor(); + $errors = array(); + + $ics_type = PhabricatorCalendarICSURIImportEngine::ENGINETYPE; + $import_type = $object->getEngine()->getImportEngineType(); + if ($import_type != $ics_type) { + if (!$xactions) { + return $errors; + } + + $errors[] = $this->newInvalidError( + pht( + 'You can not attach an ICS URI to an import type other than '. + 'an ICS URI import (type is "%s").', + $import_type)); + + return $errors; + } + + $new_value = $object->getParameter(self::PARAMKEY_URI); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + if (!strlen($new_value)) { + continue; + } + + try { + PhabricatorEnv::requireValidRemoteURIForFetch( + $new_value, + array( + 'http', + 'https', + )); + } catch (Exception $ex) { + $errors[] = $this->newInvalidError( + $ex->getMessage(), + $xaction); + } + } + + if (!strlen($new_value)) { + $errors[] = $this->newRequiredError( + pht('You must select an ".ics" URI to import.')); + } + + return $errors; + } +}