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 @@ -140,6 +140,7 @@ 'AphrontDialogView' => 'view/AphrontDialogView.php', 'AphrontEpochHTTPParameterType' => 'aphront/httpparametertype/AphrontEpochHTTPParameterType.php', 'AphrontException' => 'aphront/exception/AphrontException.php', + 'AphrontFileHTTPParameterType' => 'aphront/httpparametertype/AphrontFileHTTPParameterType.php', 'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php', 'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php', 'AphrontFormControl' => 'view/form/control/AphrontFormControl.php', @@ -2098,16 +2099,25 @@ 'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', + 'PhabricatorCalendarICSImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSImportEngine.php', 'PhabricatorCalendarICSWriter' => 'applications/calendar/util/PhabricatorCalendarICSWriter.php', 'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php', 'PhabricatorCalendarImport' => 'applications/calendar/storage/PhabricatorCalendarImport.php', + 'PhabricatorCalendarImportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php', + 'PhabricatorCalendarImportEditController' => 'applications/calendar/controller/PhabricatorCalendarImportEditController.php', + 'PhabricatorCalendarImportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarImportEditEngine.php', 'PhabricatorCalendarImportEditor' => 'applications/calendar/editor/PhabricatorCalendarImportEditor.php', + 'PhabricatorCalendarImportEngine' => 'applications/calendar/import/PhabricatorCalendarImportEngine.php', + 'PhabricatorCalendarImportICSFileTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php', 'PhabricatorCalendarImportListController' => 'applications/calendar/controller/PhabricatorCalendarImportListController.php', + 'PhabricatorCalendarImportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.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', + 'PhabricatorCalendarImportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php', + 'PhabricatorCalendarImportViewController' => 'applications/calendar/controller/PhabricatorCalendarImportViewController.php', 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', 'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php', 'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php', @@ -2582,6 +2592,7 @@ 'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php', 'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php', 'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php', + 'PhabricatorFileEditField' => 'applications/transactions/editfield/PhabricatorFileEditField.php', 'PhabricatorFileEditor' => 'applications/files/editor/PhabricatorFileEditor.php', 'PhabricatorFileExternalRequest' => 'applications/files/storage/PhabricatorFileExternalRequest.php', 'PhabricatorFileExternalRequestGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileExternalRequestGarbageCollector.php', @@ -4638,6 +4649,7 @@ ), 'AphrontEpochHTTPParameterType' => 'AphrontHTTPParameterType', 'AphrontException' => 'Exception', + 'AphrontFileHTTPParameterType' => 'AphrontHTTPParameterType', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', @@ -6886,6 +6898,7 @@ 'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', + 'PhabricatorCalendarICSImportEngine' => 'PhabricatorCalendarImportEngine', 'PhabricatorCalendarICSWriter' => 'Phobject', 'PhabricatorCalendarIconSet' => 'PhabricatorIconSet', 'PhabricatorCalendarImport' => array( @@ -6894,13 +6907,21 @@ 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), + 'PhabricatorCalendarImportDisableTransaction' => 'PhabricatorCalendarImportTransactionType', + 'PhabricatorCalendarImportEditController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarImportEditEngine' => 'PhabricatorEditEngine', 'PhabricatorCalendarImportEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorCalendarImportEngine' => 'Phobject', + 'PhabricatorCalendarImportICSFileTransaction' => 'PhabricatorCalendarImportTransactionType', 'PhabricatorCalendarImportListController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarImportNameTransaction' => 'PhabricatorCalendarImportTransactionType', 'PhabricatorCalendarImportPHIDType' => 'PhabricatorPHIDType', 'PhabricatorCalendarImportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarImportSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCalendarImportTransaction' => 'PhabricatorModularTransaction', 'PhabricatorCalendarImportTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorCalendarImportTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorCalendarImportViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec', @@ -7448,6 +7469,7 @@ 'PhabricatorFileDeleteController' => 'PhabricatorFileController', 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', 'PhabricatorFileEditController' => 'PhabricatorFileController', + 'PhabricatorFileEditField' => 'PhabricatorEditField', 'PhabricatorFileEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorFileExternalRequest' => array( 'PhabricatorFileDAO', diff --git a/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php b/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php new file mode 100644 --- /dev/null +++ b/src/aphront/httpparametertype/AphrontFileHTTPParameterType.php @@ -0,0 +1,60 @@ +getFileKey($key); + return $request->getExists($key) || + $request->getFileExists($file_key); + } + + protected function getParameterValue(AphrontRequest $request, $key) { + $value = $request->getStrList($key); + if ($value) { + return head($value); + } + + // NOTE: At least for now, we'll attempt to read a direct upload if we + // miss on a PHID. Currently, PHUIFormFileControl does a client-side + // upload on workflow forms (which is good) but doesn't have a hook for + // non-workflow forms (which isn't as good). Giving it a hook is desirable, + // but complicated. Even if we do hook it, it may be reasonable to keep + // this code around as a fallback if the client-side JS goes awry. + + $file_key = $this->getFileKey($key); + if (!$request->getFileExists($file_key)) { + return null; + } + + $viewer = $this->getViewer(); + $file = PhabricatorFile::newFromPHPUpload( + idx($_FILES, $file_key), + array( + 'authorPHID' => $viewer->getPHID(), + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + )); + return $file->getPHID(); + } + + protected function getParameterTypeName() { + return 'file'; + } + + protected function getParameterFormatDescriptions() { + return array( + pht('A file PHID.'), + ); + } + + protected function getParameterExamples() { + return array( + 'v=PHID-FILE-wxyz', + ); + } + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php b/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportEditController.php @@ -0,0 +1,92 @@ +setController($this); + + $id = $request->getURIData('id'); + if (!$id) { + $list_uri = $this->getApplicationURI('import/'); + + $import_type = $request->getStr('importType'); + $import_engines = PhabricatorCalendarImportEngine::getAllImportEngines(); + if (empty($import_engines[$import_type])) { + return $this->buildEngineTypeResponse($list_uri); + } + + $import_engine = $import_engines[$import_type]; + + $engine + ->addContextParameter('importType', $import_type) + ->setImportEngine($import_engine); + } + + return $engine->buildResponse(); + } + + private function buildEngineTypeResponse($cancel_uri) { + $import_engines = PhabricatorCalendarImportEngine::getAllImportEngines(); + + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $e_import = null; + $errors = array(); + if ($request->isFormPost()) { + $e_import = pht('Required'); + $errors[] = pht( + 'To import events, you must select a source to import from.'); + } + + $type_control = id(new AphrontFormRadioButtonControl()) + ->setLabel(pht('Import Type')) + ->setName('importType') + ->setError($e_import); + + foreach ($import_engines as $import_engine) { + $type_control->addButton( + $import_engine->getImportEngineType(), + $import_engine->getImportEngineName(), + $import_engine->getImportEngineHint()); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('New Import')); + $crumbs->setBorder(true); + + $title = pht('Choose Import Type'); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('New Import')) + ->setHeaderIcon('fa-upload'); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild($type_control) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Continue')) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setFormErrors($errors) + ->setHeaderText(pht('Import')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $box, + )); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + +} diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportListController.php b/src/applications/calendar/controller/PhabricatorCalendarImportListController.php --- a/src/applications/calendar/controller/PhabricatorCalendarImportListController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarImportListController.php @@ -9,4 +9,17 @@ ->buildResponse(); } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Import Events')) + ->setHref($this->getApplicationURI('import/edit/')) + ->setIcon('fa-upload')); + + return $crumbs; + } + + } diff --git a/src/applications/calendar/controller/PhabricatorCalendarImportViewController.php b/src/applications/calendar/controller/PhabricatorCalendarImportViewController.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarImportViewController.php @@ -0,0 +1,130 @@ +getViewer(); + + $import = id(new PhabricatorCalendarImportQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + if (!$import) { + return new Aphront404Response(); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Imports'), + '/calendar/import/'); + $crumbs->addTextCrumb(pht('Import %d', $import->getID())); + $crumbs->setBorder(true); + + $timeline = $this->buildTransactionTimeline( + $import, + new PhabricatorCalendarImportTransactionQuery()); + $timeline->setShouldTerminate(true); + + $header = $this->buildHeaderView($import); + $curtain = $this->buildCurtain($import); + $details = $this->buildPropertySection($import); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn( + array( + $timeline, + )) + ->setCurtain($curtain) + ->addPropertySection(pht('Details'), $details); + + $page_title = pht( + 'Import %d %s', + $import->getID(), + $import->getDisplayName()); + + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($import->getPHID())) + ->appendChild($view); + } + + private function buildHeaderView( + PhabricatorCalendarImport $import) { + $viewer = $this->getViewer(); + $id = $import->getID(); + + if ($import->getIsDisabled()) { + $icon = 'fa-ban'; + $color = 'red'; + $status = pht('Disabled'); + } else { + $icon = 'fa-check'; + $color = 'bluegrey'; + $status = pht('Active'); + } + + $header = id(new PHUIHeaderView()) + ->setViewer($viewer) + ->setHeader($import->getDisplayName()) + ->setStatus($icon, $color, $status) + ->setPolicyObject($import); + + return $header; + } + + private function buildCurtain(PhabricatorCalendarImport $import) { + $viewer = $this->getViewer(); + $id = $import->getID(); + + $curtain = $this->newCurtainView($import); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $import, + PhabricatorPolicyCapability::CAN_EDIT); + + $edit_uri = "import/edit/{$id}/"; + $edit_uri = $this->getApplicationURI($edit_uri); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Import')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)); + + $disable_uri = "import/disable/{$id}/"; + $disable_uri = $this->getApplicationURI($disable_uri); + if ($import->getIsDisabled()) { + $disable_name = pht('Enable Import'); + $disable_icon = 'fa-check'; + } else { + $disable_name = pht('Disable Import'); + $disable_icon = 'fa-ban'; + } + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName($disable_name) + ->setIcon($disable_icon) + ->setDisabled(!$can_edit) + ->setWorkflow(true) + ->setHref($disable_uri)); + + return $curtain; + } + + private function buildPropertySection( + PhabricatorCalendarImport $import) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setViewer($viewer); + + return $properties; + } +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php @@ -0,0 +1,115 @@ +importEngine = $engine; + return $this; + } + + public function getImportEngine() { + return $this->importEngine; + } + + public function getEngineName() { + return pht('Calendar Imports'); + } + + public function isEngineConfigurable() { + return false; + } + + public function getSummaryHeader() { + return pht('Configure Calendar Import Forms'); + } + + public function getSummaryText() { + return pht('Configure how users create and edit imports.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorCalendarApplication'; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + $engine = $this->getImportEngine(); + + return PhabricatorCalendarImport::initializeNewCalendarImport( + $viewer, + $engine); + } + + protected function newObjectQuery() { + return new PhabricatorCalendarImportQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Import'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Import: %s', $object->getDisplayName()); + } + + protected function getObjectEditShortText($object) { + return pht('Import %d', $object->getID()); + } + + protected function getObjectCreateShortText() { + return pht('Create Import'); + } + + protected function getObjectName() { + return pht('Import'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('import/edit/'); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + $fields = array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the import.')) + ->setTransactionType( + PhabricatorCalendarImportNameTransaction::TRANSACTIONTYPE) + ->setConduitDescription(pht('Rename the import.')) + ->setConduitTypeDescription(pht('New import name.')) + ->setValue($object->getName()), + id(new PhabricatorBoolEditField()) + ->setKey('disabled') + ->setOptions(pht('Active'), pht('Disabled')) + ->setLabel(pht('Disabled')) + ->setDescription(pht('Disable the import.')) + ->setTransactionType( + PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE) + ->setIsConduitOnly(true) + ->setConduitDescription(pht('Disable or restore the import.')) + ->setConduitTypeDescription(pht('True to cancel the import.')) + ->setValue($object->getIsDisabled()), + ); + + $import_engine = $object->getEngine(); + foreach ($import_engine->newEditEngineFields($this, $object) as $field) { + $fields[] = $field; + } + + return $fields; + } + + +} diff --git a/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php b/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php --- a/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarImportEditor.php @@ -15,4 +15,28 @@ return pht('%s created this import.', $author); } + public function getTransactionTypes() { + $types = parent::getTransactionTypes(); + + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; + + return $types; + } + + protected function applyFinalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + if ($this->getIsNewObject()) { + $actor = $this->getActor(); + + $import_engine = $object->getEngine(); + $import_engine->didCreateImport($actor, $object); + } + + return $xactions; + } + + } diff --git a/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php @@ -0,0 +1,74 @@ +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(); + + $parser = id(new PhutilICSParser()); + + $document = $parser->parseICSData($data); + + return $this->importEventDocument($viewer, $import, $document); + } + + + +} diff --git a/src/applications/calendar/import/PhabricatorCalendarImportEngine.php b/src/applications/calendar/import/PhabricatorCalendarImportEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/import/PhabricatorCalendarImportEngine.php @@ -0,0 +1,68 @@ +getPhobjectClassConstant('ENGINETYPE', 64); + } + + + abstract public function getImportEngineName(); + abstract public function getImportEngineHint(); + + abstract public function newEditEngineFields( + PhabricatorEditEngine $engine, + PhabricatorCalendarImport $import); + + abstract public function getDisplayName(PhabricatorCalendarImport $import); + + abstract public function didCreateImport( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import); + + final public static function getAllImportEngines() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getImportEngineType') + ->setSortMethod('getImportEngineName') + ->execute(); + } + + final protected function importEventDocument( + PhabricatorUser $viewer, + PhabricatorCalendarImport $import, + PhutilCalendarRootNode $root) { + + $event_type = PhutilCalendarEventNode::NODETYPE; + + $events = array(); + foreach ($root->getChildren() as $document) { + foreach ($document->getChildren() as $node) { + if ($node->getNodeType() != $event_type) { + // TODO: Warn that we ignored this. + continue; + } + + $event = PhabricatorCalendarEvent::newFromDocumentNode($viewer, $node); + + $event + ->setImportAuthorPHID($viewer->getPHID()) + ->setImportSourcePHID($import->getPHID()) + ->attachImportSource($import); + + $events[] = $event; + } + } + + // TODO: Use transactions. + // TODO: Update existing events instead of fataling. + foreach ($events as $event) { + $event->save(); + } + + } + + + +} 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 @@ -468,9 +468,9 @@ ->setViewer($viewer) ->withPHIDs($import_phids) ->execute(); - $sources = mpull($sources, null, 'getPHID'); + $imports = mpull($imports, null, 'getPHID'); } else { - $sources = array(); + $imports = array(); } foreach ($events as $key => $event) { diff --git a/src/applications/calendar/query/PhabricatorCalendarImportQuery.php b/src/applications/calendar/query/PhabricatorCalendarImportQuery.php --- a/src/applications/calendar/query/PhabricatorCalendarImportQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarImportQuery.php @@ -70,6 +70,24 @@ return $where; } + protected function willFilterPage(array $page) { + $engines = PhabricatorCalendarImportEngine::getAllImportEngines(); + foreach ($page as $key => $import) { + $engine_type = $import->getEngineType(); + $engine = idx($engines, $engine_type); + + if (!$engine) { + unset($page[$key]); + $this->didRejectResult($import); + continue; + } + + $import->attachEngine(clone $engine); + } + + return $page; + } + protected function getPrimaryTableAlias() { return 'import'; } diff --git a/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php --- a/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php @@ -62,7 +62,7 @@ $item = id(new PHUIObjectItemView()) ->setViewer($viewer) ->setObjectName(pht('Import %d', $import->getID())) - ->setHeader($import->getName()) + ->setHeader($import->getDisplayName()) ->setHref($import->getURI()); if ($import->getIsDisabled()) { 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 @@ -81,6 +81,7 @@ $datetime_end = $datetime_start->newRelativeDateTime('PT1H'); return id(new PhabricatorCalendarEvent()) + ->setDescription('') ->setHostPHID($actor->getPHID()) ->setIsCancelled(0) ->setIsAllDay(0) @@ -101,6 +102,66 @@ ->applyViewerTimezone($actor); } + public static function newFromDocumentNode( + PhabricatorUser $actor, + PhutilCalendarEventNode $node) { + $timezone = $actor->getTimezoneIdentifier(); + + $uid = $node->getUID(); + + $name = $node->getName(); + if (!strlen($name)) { + if (strlen($uid)) { + $name = pht('Unnamed Event "%s"', $node->getUID()); + } else { + $name = pht('Unnamed Imported Event'); + } + } + + $description = $node->getDescription(); + + $instance_iso = $node->getRecurrenceID(); + if (strlen($instance_iso)) { + $instance_datetime = PhutilCalendarAbsoluteDateTime::newFromISO8601( + $instance_iso); + $instance_epoch = $instance_datetime->getEpoch(); + } else { + $instance_epoch = null; + } + $full_uid = $uid.'/'.$instance_epoch; + + $start_datetime = $node->getStartDateTime() + ->setViewerTimezone($timezone); + $end_datetime = $node->getEndDateTime() + ->setViewerTimezone($timezone); + + $rrule = $node->getRecurrenceRule(); + + $event = self::initializeNewCalendarEvent($actor) + ->setName($name) + ->setStartDateTime($start_datetime) + ->setEndDateTime($end_datetime) + ->setImportUID($full_uid) + ->setUTCInstanceEpoch($instance_epoch); + + if (strlen($description)) { + $event->setDescription($description); + } + + if ($rrule) { + $event->setRecurrenceRule($rrule); + $event->setIsRecurring(1); + + $until_datetime = $rrule->getUntil() + ->setViewerTimezone($timezone); + if ($until_datetime) { + $event->setUntilDateTime($until_datetime); + } + } + + return $event; + } + private function newChild( PhabricatorUser $actor, $sequence, @@ -980,7 +1041,7 @@ return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: if ($this->getImportSource()) { - return PhabricatorPolicy::POLICY_NOONE; + return PhabricatorPolicies::POLICY_NOONE; } else { return $this->getEditPolicy(); } diff --git a/src/applications/calendar/storage/PhabricatorCalendarImport.php b/src/applications/calendar/storage/PhabricatorCalendarImport.php --- a/src/applications/calendar/storage/PhabricatorCalendarImport.php +++ b/src/applications/calendar/storage/PhabricatorCalendarImport.php @@ -17,12 +17,16 @@ private $engine = self::ATTACHABLE; - public static function initializeNewCalendarImport(PhabricatorUser $actor) { + public static function initializeNewCalendarImport( + PhabricatorUser $actor, + PhabricatorCalendarImportEngine $engine) { return id(new self()) ->setAuthorPHID($actor->getPHID()) ->setViewPolicy($actor->getPHID()) ->setEditPolicy($actor->getPHID()) - ->setIsDisabled(0); + ->setIsDisabled(0) + ->setEngineType($engine->getImportEngineType()) + ->attachEngine($engine); } protected function getConfiguration() { @@ -53,6 +57,33 @@ return "/calendar/import/{$id}/"; } + public function attachEngine(PhabricatorCalendarImportEngine $engine) { + $this->engine = $engine; + return $this; + } + + public function getEngine() { + return $this->assertAttached($this->engine); + } + + public function getParameter($key, $default = null) { + return idx($this->parameters, $key, $default); + } + + public function setParameter($key, $value) { + $this->parameters[$key] = $value; + return $this; + } + + public function getDisplayName() { + $name = $this->getName(); + if (strlen($name)) { + return $name; + } + + return $this->getEngine()->getDisplayName($this); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php @@ -0,0 +1,28 @@ +getIsDisabled(); + } + + public function applyInternalEffects($object, $value) { + $object->setIsDisabled((int)$value); + } + + public function getTitle() { + if ($this->getNewValue()) { + return pht( + '%s disabled this import.', + $this->renderAuthor()); + } else { + return pht( + '%s enabled this import.', + $this->renderAuthor()); + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php @@ -0,0 +1,80 @@ +getParameter(self::PARAMKEY_FILE); + } + + public function applyInternalEffects($object, $value) { + $object->setParameter(self::PARAMKEY_FILE, $value); + + $viewer = $this->getActor(); + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($value)) + ->executeOne(); + if ($file) { + $object->setParameter(self::PARAMKEY_NAME, $file->getName()); + } + } + + public function getTitle() { + return pht( + '%s imported an ICS file.', + $this->renderAuthor()); + } + + public function validateTransactions($object, array $xactions) { + $viewer = $this->getActor(); + $errors = array(); + + $ics_type = PhabricatorCalendarICSImportEngine::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 file to an import type other than '. + 'an ICS import (type is "%s").', + $import_type)); + + return $errors; + } + + $new_value = $object->getParameter(self::PARAMKEY_FILE); + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + if (!strlen($new_value)) { + continue; + } + + $file = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($new_value)) + ->executeOne(); + if (!$file) { + $errors[] = $this->newInvalidError( + pht( + 'File PHID "%s" is not valid or not visible.', + $new_value), + $xaction); + } + } + + if (!$new_value) { + $errors[] = $this->newRequiredError( + pht('You must select an ".ics" file to import.')); + } + + return $errors; + } +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php b/src/applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php @@ -0,0 +1,39 @@ +getName(); + } + + public function applyInternalEffects($object, $value) { + $object->setName($value); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (!strlen($old)) { + return pht( + '%s named this import %s.', + $this->renderAuthor(), + $this->renderNewValue()); + } else if (!strlen($new)) { + return pht( + '%s removed the name of this import (was: %s).', + $this->renderAuthor(), + $this->renderOldValue()); + } else { + return pht( + '%s renamed this import from %s to %s.', + $this->renderAuthor(), + $this->renderOldValue(), + $this->renderNewValue()); + } + } + +} diff --git a/src/applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php b/src/applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php @@ -0,0 +1,4 @@ +setEncType('multipart/form-data'); + return parent::appendToForm($form); + } + +}