@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make Calendar ICS imports sort of work in a crude, approximate way

Summary: Ref T10747. This barely works, but can technically import some event data.

Test Plan: Used import flow to import a ".ics" document.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10747

Differential Revision: https://secure.phabricator.com/D16699

+888 -6
+22
src/__phutil_library_map__.php
··· 140 140 'AphrontDialogView' => 'view/AphrontDialogView.php', 141 141 'AphrontEpochHTTPParameterType' => 'aphront/httpparametertype/AphrontEpochHTTPParameterType.php', 142 142 'AphrontException' => 'aphront/exception/AphrontException.php', 143 + 'AphrontFileHTTPParameterType' => 'aphront/httpparametertype/AphrontFileHTTPParameterType.php', 143 144 'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php', 144 145 'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php', 145 146 'AphrontFormControl' => 'view/form/control/AphrontFormControl.php', ··· 2098 2099 'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php', 2099 2100 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 2100 2101 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 2102 + 'PhabricatorCalendarICSImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSImportEngine.php', 2101 2103 'PhabricatorCalendarICSWriter' => 'applications/calendar/util/PhabricatorCalendarICSWriter.php', 2102 2104 'PhabricatorCalendarIconSet' => 'applications/calendar/icon/PhabricatorCalendarIconSet.php', 2103 2105 'PhabricatorCalendarImport' => 'applications/calendar/storage/PhabricatorCalendarImport.php', 2106 + 'PhabricatorCalendarImportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php', 2107 + 'PhabricatorCalendarImportEditController' => 'applications/calendar/controller/PhabricatorCalendarImportEditController.php', 2108 + 'PhabricatorCalendarImportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarImportEditEngine.php', 2104 2109 'PhabricatorCalendarImportEditor' => 'applications/calendar/editor/PhabricatorCalendarImportEditor.php', 2110 + 'PhabricatorCalendarImportEngine' => 'applications/calendar/import/PhabricatorCalendarImportEngine.php', 2111 + 'PhabricatorCalendarImportICSFileTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php', 2105 2112 'PhabricatorCalendarImportListController' => 'applications/calendar/controller/PhabricatorCalendarImportListController.php', 2113 + 'PhabricatorCalendarImportNameTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php', 2106 2114 'PhabricatorCalendarImportPHIDType' => 'applications/calendar/phid/PhabricatorCalendarImportPHIDType.php', 2107 2115 'PhabricatorCalendarImportQuery' => 'applications/calendar/query/PhabricatorCalendarImportQuery.php', 2108 2116 'PhabricatorCalendarImportSearchEngine' => 'applications/calendar/query/PhabricatorCalendarImportSearchEngine.php', 2109 2117 'PhabricatorCalendarImportTransaction' => 'applications/calendar/storage/PhabricatorCalendarImportTransaction.php', 2110 2118 'PhabricatorCalendarImportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarImportTransactionQuery.php', 2119 + 'PhabricatorCalendarImportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php', 2120 + 'PhabricatorCalendarImportViewController' => 'applications/calendar/controller/PhabricatorCalendarImportViewController.php', 2111 2121 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', 2112 2122 'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php', 2113 2123 'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php', ··· 2582 2592 'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php', 2583 2593 'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php', 2584 2594 'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php', 2595 + 'PhabricatorFileEditField' => 'applications/transactions/editfield/PhabricatorFileEditField.php', 2585 2596 'PhabricatorFileEditor' => 'applications/files/editor/PhabricatorFileEditor.php', 2586 2597 'PhabricatorFileExternalRequest' => 'applications/files/storage/PhabricatorFileExternalRequest.php', 2587 2598 'PhabricatorFileExternalRequestGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileExternalRequestGarbageCollector.php', ··· 4638 4649 ), 4639 4650 'AphrontEpochHTTPParameterType' => 'AphrontHTTPParameterType', 4640 4651 'AphrontException' => 'Exception', 4652 + 'AphrontFileHTTPParameterType' => 'AphrontHTTPParameterType', 4641 4653 'AphrontFileResponse' => 'AphrontResponse', 4642 4654 'AphrontFormCheckboxControl' => 'AphrontFormControl', 4643 4655 'AphrontFormControl' => 'AphrontView', ··· 6886 6898 'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController', 6887 6899 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 6888 6900 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 6901 + 'PhabricatorCalendarICSImportEngine' => 'PhabricatorCalendarImportEngine', 6889 6902 'PhabricatorCalendarICSWriter' => 'Phobject', 6890 6903 'PhabricatorCalendarIconSet' => 'PhabricatorIconSet', 6891 6904 'PhabricatorCalendarImport' => array( ··· 6894 6907 'PhabricatorApplicationTransactionInterface', 6895 6908 'PhabricatorDestructibleInterface', 6896 6909 ), 6910 + 'PhabricatorCalendarImportDisableTransaction' => 'PhabricatorCalendarImportTransactionType', 6911 + 'PhabricatorCalendarImportEditController' => 'PhabricatorCalendarController', 6912 + 'PhabricatorCalendarImportEditEngine' => 'PhabricatorEditEngine', 6897 6913 'PhabricatorCalendarImportEditor' => 'PhabricatorApplicationTransactionEditor', 6914 + 'PhabricatorCalendarImportEngine' => 'Phobject', 6915 + 'PhabricatorCalendarImportICSFileTransaction' => 'PhabricatorCalendarImportTransactionType', 6898 6916 'PhabricatorCalendarImportListController' => 'PhabricatorCalendarController', 6917 + 'PhabricatorCalendarImportNameTransaction' => 'PhabricatorCalendarImportTransactionType', 6899 6918 'PhabricatorCalendarImportPHIDType' => 'PhabricatorPHIDType', 6900 6919 'PhabricatorCalendarImportQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6901 6920 'PhabricatorCalendarImportSearchEngine' => 'PhabricatorApplicationSearchEngine', 6902 6921 'PhabricatorCalendarImportTransaction' => 'PhabricatorModularTransaction', 6903 6922 'PhabricatorCalendarImportTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 6923 + 'PhabricatorCalendarImportTransactionType' => 'PhabricatorModularTransactionType', 6924 + 'PhabricatorCalendarImportViewController' => 'PhabricatorCalendarController', 6904 6925 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', 6905 6926 'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 6906 6927 'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec', ··· 7448 7469 'PhabricatorFileDeleteController' => 'PhabricatorFileController', 7449 7470 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', 7450 7471 'PhabricatorFileEditController' => 'PhabricatorFileController', 7472 + 'PhabricatorFileEditField' => 'PhabricatorEditField', 7451 7473 'PhabricatorFileEditor' => 'PhabricatorApplicationTransactionEditor', 7452 7474 'PhabricatorFileExternalRequest' => array( 7453 7475 'PhabricatorFileDAO',
+60
src/aphront/httpparametertype/AphrontFileHTTPParameterType.php
··· 1 + <?php 2 + 3 + final class AphrontFileHTTPParameterType 4 + extends AphrontHTTPParameterType { 5 + 6 + private function getFileKey($key) { 7 + return $key.'_raw'; 8 + } 9 + 10 + protected function getParameterExists(AphrontRequest $request, $key) { 11 + $file_key = $this->getFileKey($key); 12 + return $request->getExists($key) || 13 + $request->getFileExists($file_key); 14 + } 15 + 16 + protected function getParameterValue(AphrontRequest $request, $key) { 17 + $value = $request->getStrList($key); 18 + if ($value) { 19 + return head($value); 20 + } 21 + 22 + // NOTE: At least for now, we'll attempt to read a direct upload if we 23 + // miss on a PHID. Currently, PHUIFormFileControl does a client-side 24 + // upload on workflow forms (which is good) but doesn't have a hook for 25 + // non-workflow forms (which isn't as good). Giving it a hook is desirable, 26 + // but complicated. Even if we do hook it, it may be reasonable to keep 27 + // this code around as a fallback if the client-side JS goes awry. 28 + 29 + $file_key = $this->getFileKey($key); 30 + if (!$request->getFileExists($file_key)) { 31 + return null; 32 + } 33 + 34 + $viewer = $this->getViewer(); 35 + $file = PhabricatorFile::newFromPHPUpload( 36 + idx($_FILES, $file_key), 37 + array( 38 + 'authorPHID' => $viewer->getPHID(), 39 + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, 40 + )); 41 + return $file->getPHID(); 42 + } 43 + 44 + protected function getParameterTypeName() { 45 + return 'file'; 46 + } 47 + 48 + protected function getParameterFormatDescriptions() { 49 + return array( 50 + pht('A file PHID.'), 51 + ); 52 + } 53 + 54 + protected function getParameterExamples() { 55 + return array( 56 + 'v=PHID-FILE-wxyz', 57 + ); 58 + } 59 + 60 + }
+92
src/applications/calendar/controller/PhabricatorCalendarImportEditController.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportEditController 4 + extends PhabricatorCalendarController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $engine = id(new PhabricatorCalendarImportEditEngine()) 8 + ->setController($this); 9 + 10 + $id = $request->getURIData('id'); 11 + if (!$id) { 12 + $list_uri = $this->getApplicationURI('import/'); 13 + 14 + $import_type = $request->getStr('importType'); 15 + $import_engines = PhabricatorCalendarImportEngine::getAllImportEngines(); 16 + if (empty($import_engines[$import_type])) { 17 + return $this->buildEngineTypeResponse($list_uri); 18 + } 19 + 20 + $import_engine = $import_engines[$import_type]; 21 + 22 + $engine 23 + ->addContextParameter('importType', $import_type) 24 + ->setImportEngine($import_engine); 25 + } 26 + 27 + return $engine->buildResponse(); 28 + } 29 + 30 + private function buildEngineTypeResponse($cancel_uri) { 31 + $import_engines = PhabricatorCalendarImportEngine::getAllImportEngines(); 32 + 33 + $request = $this->getRequest(); 34 + $viewer = $this->getViewer(); 35 + 36 + $e_import = null; 37 + $errors = array(); 38 + if ($request->isFormPost()) { 39 + $e_import = pht('Required'); 40 + $errors[] = pht( 41 + 'To import events, you must select a source to import from.'); 42 + } 43 + 44 + $type_control = id(new AphrontFormRadioButtonControl()) 45 + ->setLabel(pht('Import Type')) 46 + ->setName('importType') 47 + ->setError($e_import); 48 + 49 + foreach ($import_engines as $import_engine) { 50 + $type_control->addButton( 51 + $import_engine->getImportEngineType(), 52 + $import_engine->getImportEngineName(), 53 + $import_engine->getImportEngineHint()); 54 + } 55 + 56 + $crumbs = $this->buildApplicationCrumbs(); 57 + $crumbs->addTextCrumb(pht('New Import')); 58 + $crumbs->setBorder(true); 59 + 60 + $title = pht('Choose Import Type'); 61 + $header = id(new PHUIHeaderView()) 62 + ->setHeader(pht('New Import')) 63 + ->setHeaderIcon('fa-upload'); 64 + 65 + $form = id(new AphrontFormView()) 66 + ->setUser($viewer) 67 + ->appendChild($type_control) 68 + ->appendChild( 69 + id(new AphrontFormSubmitControl()) 70 + ->setValue(pht('Continue')) 71 + ->addCancelButton($cancel_uri)); 72 + 73 + $box = id(new PHUIObjectBoxView()) 74 + ->setFormErrors($errors) 75 + ->setHeaderText(pht('Import')) 76 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 77 + ->setForm($form); 78 + 79 + $view = id(new PHUITwoColumnView()) 80 + ->setHeader($header) 81 + ->setFooter( 82 + array( 83 + $box, 84 + )); 85 + 86 + return $this->newPage() 87 + ->setTitle($title) 88 + ->setCrumbs($crumbs) 89 + ->appendChild($view); 90 + } 91 + 92 + }
+13
src/applications/calendar/controller/PhabricatorCalendarImportListController.php
··· 9 9 ->buildResponse(); 10 10 } 11 11 12 + protected function buildApplicationCrumbs() { 13 + $crumbs = parent::buildApplicationCrumbs(); 14 + 15 + $crumbs->addAction( 16 + id(new PHUIListItemView()) 17 + ->setName(pht('Import Events')) 18 + ->setHref($this->getApplicationURI('import/edit/')) 19 + ->setIcon('fa-upload')); 20 + 21 + return $crumbs; 22 + } 23 + 24 + 12 25 }
+130
src/applications/calendar/controller/PhabricatorCalendarImportViewController.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportViewController 4 + extends PhabricatorCalendarController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + $import = id(new PhabricatorCalendarImportQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($request->getURIData('id'))) 12 + ->executeOne(); 13 + if (!$import) { 14 + return new Aphront404Response(); 15 + } 16 + 17 + $crumbs = $this->buildApplicationCrumbs(); 18 + $crumbs->addTextCrumb( 19 + pht('Imports'), 20 + '/calendar/import/'); 21 + $crumbs->addTextCrumb(pht('Import %d', $import->getID())); 22 + $crumbs->setBorder(true); 23 + 24 + $timeline = $this->buildTransactionTimeline( 25 + $import, 26 + new PhabricatorCalendarImportTransactionQuery()); 27 + $timeline->setShouldTerminate(true); 28 + 29 + $header = $this->buildHeaderView($import); 30 + $curtain = $this->buildCurtain($import); 31 + $details = $this->buildPropertySection($import); 32 + 33 + $view = id(new PHUITwoColumnView()) 34 + ->setHeader($header) 35 + ->setMainColumn( 36 + array( 37 + $timeline, 38 + )) 39 + ->setCurtain($curtain) 40 + ->addPropertySection(pht('Details'), $details); 41 + 42 + $page_title = pht( 43 + 'Import %d %s', 44 + $import->getID(), 45 + $import->getDisplayName()); 46 + 47 + return $this->newPage() 48 + ->setTitle($page_title) 49 + ->setCrumbs($crumbs) 50 + ->setPageObjectPHIDs(array($import->getPHID())) 51 + ->appendChild($view); 52 + } 53 + 54 + private function buildHeaderView( 55 + PhabricatorCalendarImport $import) { 56 + $viewer = $this->getViewer(); 57 + $id = $import->getID(); 58 + 59 + if ($import->getIsDisabled()) { 60 + $icon = 'fa-ban'; 61 + $color = 'red'; 62 + $status = pht('Disabled'); 63 + } else { 64 + $icon = 'fa-check'; 65 + $color = 'bluegrey'; 66 + $status = pht('Active'); 67 + } 68 + 69 + $header = id(new PHUIHeaderView()) 70 + ->setViewer($viewer) 71 + ->setHeader($import->getDisplayName()) 72 + ->setStatus($icon, $color, $status) 73 + ->setPolicyObject($import); 74 + 75 + return $header; 76 + } 77 + 78 + private function buildCurtain(PhabricatorCalendarImport $import) { 79 + $viewer = $this->getViewer(); 80 + $id = $import->getID(); 81 + 82 + $curtain = $this->newCurtainView($import); 83 + 84 + $can_edit = PhabricatorPolicyFilter::hasCapability( 85 + $viewer, 86 + $import, 87 + PhabricatorPolicyCapability::CAN_EDIT); 88 + 89 + $edit_uri = "import/edit/{$id}/"; 90 + $edit_uri = $this->getApplicationURI($edit_uri); 91 + 92 + $curtain->addAction( 93 + id(new PhabricatorActionView()) 94 + ->setName(pht('Edit Import')) 95 + ->setIcon('fa-pencil') 96 + ->setDisabled(!$can_edit) 97 + ->setWorkflow(!$can_edit) 98 + ->setHref($edit_uri)); 99 + 100 + $disable_uri = "import/disable/{$id}/"; 101 + $disable_uri = $this->getApplicationURI($disable_uri); 102 + if ($import->getIsDisabled()) { 103 + $disable_name = pht('Enable Import'); 104 + $disable_icon = 'fa-check'; 105 + } else { 106 + $disable_name = pht('Disable Import'); 107 + $disable_icon = 'fa-ban'; 108 + } 109 + 110 + $curtain->addAction( 111 + id(new PhabricatorActionView()) 112 + ->setName($disable_name) 113 + ->setIcon($disable_icon) 114 + ->setDisabled(!$can_edit) 115 + ->setWorkflow(true) 116 + ->setHref($disable_uri)); 117 + 118 + return $curtain; 119 + } 120 + 121 + private function buildPropertySection( 122 + PhabricatorCalendarImport $import) { 123 + $viewer = $this->getViewer(); 124 + 125 + $properties = id(new PHUIPropertyListView()) 126 + ->setViewer($viewer); 127 + 128 + return $properties; 129 + } 130 + }
+115
src/applications/calendar/editor/PhabricatorCalendarImportEditEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportEditEngine 4 + extends PhabricatorEditEngine { 5 + 6 + const ENGINECONST = 'calendar.import'; 7 + 8 + private $importEngine; 9 + 10 + public function setImportEngine(PhabricatorCalendarImportEngine $engine) { 11 + $this->importEngine = $engine; 12 + return $this; 13 + } 14 + 15 + public function getImportEngine() { 16 + return $this->importEngine; 17 + } 18 + 19 + public function getEngineName() { 20 + return pht('Calendar Imports'); 21 + } 22 + 23 + public function isEngineConfigurable() { 24 + return false; 25 + } 26 + 27 + public function getSummaryHeader() { 28 + return pht('Configure Calendar Import Forms'); 29 + } 30 + 31 + public function getSummaryText() { 32 + return pht('Configure how users create and edit imports.'); 33 + } 34 + 35 + public function getEngineApplicationClass() { 36 + return 'PhabricatorCalendarApplication'; 37 + } 38 + 39 + protected function newEditableObject() { 40 + $viewer = $this->getViewer(); 41 + $engine = $this->getImportEngine(); 42 + 43 + return PhabricatorCalendarImport::initializeNewCalendarImport( 44 + $viewer, 45 + $engine); 46 + } 47 + 48 + protected function newObjectQuery() { 49 + return new PhabricatorCalendarImportQuery(); 50 + } 51 + 52 + protected function getObjectCreateTitleText($object) { 53 + return pht('Create New Import'); 54 + } 55 + 56 + protected function getObjectEditTitleText($object) { 57 + return pht('Edit Import: %s', $object->getDisplayName()); 58 + } 59 + 60 + protected function getObjectEditShortText($object) { 61 + return pht('Import %d', $object->getID()); 62 + } 63 + 64 + protected function getObjectCreateShortText() { 65 + return pht('Create Import'); 66 + } 67 + 68 + protected function getObjectName() { 69 + return pht('Import'); 70 + } 71 + 72 + protected function getObjectViewURI($object) { 73 + return $object->getURI(); 74 + } 75 + 76 + protected function getEditorURI() { 77 + return $this->getApplication()->getApplicationURI('import/edit/'); 78 + } 79 + 80 + protected function buildCustomEditFields($object) { 81 + $viewer = $this->getViewer(); 82 + 83 + $fields = array( 84 + id(new PhabricatorTextEditField()) 85 + ->setKey('name') 86 + ->setLabel(pht('Name')) 87 + ->setDescription(pht('Name of the import.')) 88 + ->setTransactionType( 89 + PhabricatorCalendarImportNameTransaction::TRANSACTIONTYPE) 90 + ->setConduitDescription(pht('Rename the import.')) 91 + ->setConduitTypeDescription(pht('New import name.')) 92 + ->setValue($object->getName()), 93 + id(new PhabricatorBoolEditField()) 94 + ->setKey('disabled') 95 + ->setOptions(pht('Active'), pht('Disabled')) 96 + ->setLabel(pht('Disabled')) 97 + ->setDescription(pht('Disable the import.')) 98 + ->setTransactionType( 99 + PhabricatorCalendarImportDisableTransaction::TRANSACTIONTYPE) 100 + ->setIsConduitOnly(true) 101 + ->setConduitDescription(pht('Disable or restore the import.')) 102 + ->setConduitTypeDescription(pht('True to cancel the import.')) 103 + ->setValue($object->getIsDisabled()), 104 + ); 105 + 106 + $import_engine = $object->getEngine(); 107 + foreach ($import_engine->newEditEngineFields($this, $object) as $field) { 108 + $fields[] = $field; 109 + } 110 + 111 + return $fields; 112 + } 113 + 114 + 115 + }
+24
src/applications/calendar/editor/PhabricatorCalendarImportEditor.php
··· 15 15 return pht('%s created this import.', $author); 16 16 } 17 17 18 + public function getTransactionTypes() { 19 + $types = parent::getTransactionTypes(); 20 + 21 + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 22 + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 23 + 24 + return $types; 25 + } 26 + 27 + protected function applyFinalEffects( 28 + PhabricatorLiskDAO $object, 29 + array $xactions) { 30 + 31 + if ($this->getIsNewObject()) { 32 + $actor = $this->getActor(); 33 + 34 + $import_engine = $object->getEngine(); 35 + $import_engine->didCreateImport($actor, $object); 36 + } 37 + 38 + return $xactions; 39 + } 40 + 41 + 18 42 }
+74
src/applications/calendar/import/PhabricatorCalendarICSImportEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarICSImportEngine 4 + extends PhabricatorCalendarImportEngine { 5 + 6 + const ENGINETYPE = 'ics'; 7 + 8 + public function getImportEngineName() { 9 + return pht('Import .ics File'); 10 + } 11 + 12 + public function getImportEngineHint() { 13 + return pht('Import an event in ".ics" (iCalendar) format.'); 14 + } 15 + 16 + public function newEditEngineFields( 17 + PhabricatorEditEngine $engine, 18 + PhabricatorCalendarImport $import) { 19 + $fields = array(); 20 + 21 + if ($engine->getIsCreate()) { 22 + $fields[] = id(new PhabricatorFileEditField()) 23 + ->setKey('icsFilePHID') 24 + ->setLabel(pht('ICS File')) 25 + ->setDescription(pht('ICS file to import.')) 26 + ->setTransactionType( 27 + PhabricatorCalendarImportICSFileTransaction::TRANSACTIONTYPE) 28 + ->setConduitDescription(pht('File PHID to import.')) 29 + ->setConduitTypeDescription(pht('File PHID.')); 30 + } 31 + 32 + return $fields; 33 + } 34 + 35 + public function getDisplayName(PhabricatorCalendarImport $import) { 36 + $filename_key = PhabricatorCalendarImportICSFileTransaction::PARAMKEY_NAME; 37 + $filename = $import->getParameter($filename_key); 38 + if (strlen($filename)) { 39 + return pht('ICS File "%s"', $filename); 40 + } else { 41 + return pht('ICS File'); 42 + } 43 + } 44 + 45 + public function didCreateImport( 46 + PhabricatorUser $viewer, 47 + PhabricatorCalendarImport $import) { 48 + 49 + $phid_key = PhabricatorCalendarImportICSFileTransaction::PARAMKEY_FILE; 50 + $file_phid = $import->getParameter($phid_key); 51 + 52 + $file = id(new PhabricatorFileQuery()) 53 + ->setViewer($viewer) 54 + ->withPHIDs(array($file_phid)) 55 + ->executeOne(); 56 + if (!$file) { 57 + throw new Exception( 58 + pht( 59 + 'Unable to load file ("%s") for import.', 60 + $file_phid)); 61 + } 62 + 63 + $data = $file->loadFileData(); 64 + 65 + $parser = id(new PhutilICSParser()); 66 + 67 + $document = $parser->parseICSData($data); 68 + 69 + return $this->importEventDocument($viewer, $import, $document); 70 + } 71 + 72 + 73 + 74 + }
+68
src/applications/calendar/import/PhabricatorCalendarImportEngine.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorCalendarImportEngine 4 + extends Phobject { 5 + 6 + final public function getImportEngineType() { 7 + return $this->getPhobjectClassConstant('ENGINETYPE', 64); 8 + } 9 + 10 + 11 + abstract public function getImportEngineName(); 12 + abstract public function getImportEngineHint(); 13 + 14 + abstract public function newEditEngineFields( 15 + PhabricatorEditEngine $engine, 16 + PhabricatorCalendarImport $import); 17 + 18 + abstract public function getDisplayName(PhabricatorCalendarImport $import); 19 + 20 + abstract public function didCreateImport( 21 + PhabricatorUser $viewer, 22 + PhabricatorCalendarImport $import); 23 + 24 + final public static function getAllImportEngines() { 25 + return id(new PhutilClassMapQuery()) 26 + ->setAncestorClass(__CLASS__) 27 + ->setUniqueMethod('getImportEngineType') 28 + ->setSortMethod('getImportEngineName') 29 + ->execute(); 30 + } 31 + 32 + final protected function importEventDocument( 33 + PhabricatorUser $viewer, 34 + PhabricatorCalendarImport $import, 35 + PhutilCalendarRootNode $root) { 36 + 37 + $event_type = PhutilCalendarEventNode::NODETYPE; 38 + 39 + $events = array(); 40 + foreach ($root->getChildren() as $document) { 41 + foreach ($document->getChildren() as $node) { 42 + if ($node->getNodeType() != $event_type) { 43 + // TODO: Warn that we ignored this. 44 + continue; 45 + } 46 + 47 + $event = PhabricatorCalendarEvent::newFromDocumentNode($viewer, $node); 48 + 49 + $event 50 + ->setImportAuthorPHID($viewer->getPHID()) 51 + ->setImportSourcePHID($import->getPHID()) 52 + ->attachImportSource($import); 53 + 54 + $events[] = $event; 55 + } 56 + } 57 + 58 + // TODO: Use transactions. 59 + // TODO: Update existing events instead of fataling. 60 + foreach ($events as $event) { 61 + $event->save(); 62 + } 63 + 64 + } 65 + 66 + 67 + 68 + }
+2 -2
src/applications/calendar/query/PhabricatorCalendarEventQuery.php
··· 468 468 ->setViewer($viewer) 469 469 ->withPHIDs($import_phids) 470 470 ->execute(); 471 - $sources = mpull($sources, null, 'getPHID'); 471 + $imports = mpull($imports, null, 'getPHID'); 472 472 } else { 473 - $sources = array(); 473 + $imports = array(); 474 474 } 475 475 476 476 foreach ($events as $key => $event) {
+18
src/applications/calendar/query/PhabricatorCalendarImportQuery.php
··· 70 70 return $where; 71 71 } 72 72 73 + protected function willFilterPage(array $page) { 74 + $engines = PhabricatorCalendarImportEngine::getAllImportEngines(); 75 + foreach ($page as $key => $import) { 76 + $engine_type = $import->getEngineType(); 77 + $engine = idx($engines, $engine_type); 78 + 79 + if (!$engine) { 80 + unset($page[$key]); 81 + $this->didRejectResult($import); 82 + continue; 83 + } 84 + 85 + $import->attachEngine(clone $engine); 86 + } 87 + 88 + return $page; 89 + } 90 + 73 91 protected function getPrimaryTableAlias() { 74 92 return 'import'; 75 93 }
+1 -1
src/applications/calendar/query/PhabricatorCalendarImportSearchEngine.php
··· 62 62 $item = id(new PHUIObjectItemView()) 63 63 ->setViewer($viewer) 64 64 ->setObjectName(pht('Import %d', $import->getID())) 65 - ->setHeader($import->getName()) 65 + ->setHeader($import->getDisplayName()) 66 66 ->setHref($import->getURI()); 67 67 68 68 if ($import->getIsDisabled()) {
+62 -1
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 81 81 $datetime_end = $datetime_start->newRelativeDateTime('PT1H'); 82 82 83 83 return id(new PhabricatorCalendarEvent()) 84 + ->setDescription('') 84 85 ->setHostPHID($actor->getPHID()) 85 86 ->setIsCancelled(0) 86 87 ->setIsAllDay(0) ··· 99 100 ->setEndDateTime($datetime_end) 100 101 ->attachImportSource(null) 101 102 ->applyViewerTimezone($actor); 103 + } 104 + 105 + public static function newFromDocumentNode( 106 + PhabricatorUser $actor, 107 + PhutilCalendarEventNode $node) { 108 + $timezone = $actor->getTimezoneIdentifier(); 109 + 110 + $uid = $node->getUID(); 111 + 112 + $name = $node->getName(); 113 + if (!strlen($name)) { 114 + if (strlen($uid)) { 115 + $name = pht('Unnamed Event "%s"', $node->getUID()); 116 + } else { 117 + $name = pht('Unnamed Imported Event'); 118 + } 119 + } 120 + 121 + $description = $node->getDescription(); 122 + 123 + $instance_iso = $node->getRecurrenceID(); 124 + if (strlen($instance_iso)) { 125 + $instance_datetime = PhutilCalendarAbsoluteDateTime::newFromISO8601( 126 + $instance_iso); 127 + $instance_epoch = $instance_datetime->getEpoch(); 128 + } else { 129 + $instance_epoch = null; 130 + } 131 + $full_uid = $uid.'/'.$instance_epoch; 132 + 133 + $start_datetime = $node->getStartDateTime() 134 + ->setViewerTimezone($timezone); 135 + $end_datetime = $node->getEndDateTime() 136 + ->setViewerTimezone($timezone); 137 + 138 + $rrule = $node->getRecurrenceRule(); 139 + 140 + $event = self::initializeNewCalendarEvent($actor) 141 + ->setName($name) 142 + ->setStartDateTime($start_datetime) 143 + ->setEndDateTime($end_datetime) 144 + ->setImportUID($full_uid) 145 + ->setUTCInstanceEpoch($instance_epoch); 146 + 147 + if (strlen($description)) { 148 + $event->setDescription($description); 149 + } 150 + 151 + if ($rrule) { 152 + $event->setRecurrenceRule($rrule); 153 + $event->setIsRecurring(1); 154 + 155 + $until_datetime = $rrule->getUntil() 156 + ->setViewerTimezone($timezone); 157 + if ($until_datetime) { 158 + $event->setUntilDateTime($until_datetime); 159 + } 160 + } 161 + 162 + return $event; 102 163 } 103 164 104 165 private function newChild( ··· 980 1041 return $this->getViewPolicy(); 981 1042 case PhabricatorPolicyCapability::CAN_EDIT: 982 1043 if ($this->getImportSource()) { 983 - return PhabricatorPolicy::POLICY_NOONE; 1044 + return PhabricatorPolicies::POLICY_NOONE; 984 1045 } else { 985 1046 return $this->getEditPolicy(); 986 1047 }
+33 -2
src/applications/calendar/storage/PhabricatorCalendarImport.php
··· 17 17 18 18 private $engine = self::ATTACHABLE; 19 19 20 - public static function initializeNewCalendarImport(PhabricatorUser $actor) { 20 + public static function initializeNewCalendarImport( 21 + PhabricatorUser $actor, 22 + PhabricatorCalendarImportEngine $engine) { 21 23 return id(new self()) 22 24 ->setAuthorPHID($actor->getPHID()) 23 25 ->setViewPolicy($actor->getPHID()) 24 26 ->setEditPolicy($actor->getPHID()) 25 - ->setIsDisabled(0); 27 + ->setIsDisabled(0) 28 + ->setEngineType($engine->getImportEngineType()) 29 + ->attachEngine($engine); 26 30 } 27 31 28 32 protected function getConfiguration() { ··· 51 55 public function getURI() { 52 56 $id = $this->getID(); 53 57 return "/calendar/import/{$id}/"; 58 + } 59 + 60 + public function attachEngine(PhabricatorCalendarImportEngine $engine) { 61 + $this->engine = $engine; 62 + return $this; 63 + } 64 + 65 + public function getEngine() { 66 + return $this->assertAttached($this->engine); 67 + } 68 + 69 + public function getParameter($key, $default = null) { 70 + return idx($this->parameters, $key, $default); 71 + } 72 + 73 + public function setParameter($key, $value) { 74 + $this->parameters[$key] = $value; 75 + return $this; 76 + } 77 + 78 + public function getDisplayName() { 79 + $name = $this->getName(); 80 + if (strlen($name)) { 81 + return $name; 82 + } 83 + 84 + return $this->getEngine()->getDisplayName($this); 54 85 } 55 86 56 87 /* -( PhabricatorPolicyInterface )----------------------------------------- */
+28
src/applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportDisableTransaction 4 + extends PhabricatorCalendarImportTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'calendar.import.disable'; 7 + 8 + public function generateOldValue($object) { 9 + return (int)$object->getIsDisabled(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setIsDisabled((int)$value); 14 + } 15 + 16 + public function getTitle() { 17 + if ($this->getNewValue()) { 18 + return pht( 19 + '%s disabled this import.', 20 + $this->renderAuthor()); 21 + } else { 22 + return pht( 23 + '%s enabled this import.', 24 + $this->renderAuthor()); 25 + } 26 + } 27 + 28 + }
+80
src/applications/calendar/xaction/PhabricatorCalendarImportICSFileTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportICSFileTransaction 4 + extends PhabricatorCalendarImportTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'calendar.import.ics.file'; 7 + const PARAMKEY_FILE = 'ics.filePHID'; 8 + const PARAMKEY_NAME = 'ics.fileName'; 9 + 10 + public function generateOldValue($object) { 11 + return $object->getParameter(self::PARAMKEY_FILE); 12 + } 13 + 14 + public function applyInternalEffects($object, $value) { 15 + $object->setParameter(self::PARAMKEY_FILE, $value); 16 + 17 + $viewer = $this->getActor(); 18 + $file = id(new PhabricatorFileQuery()) 19 + ->setViewer($viewer) 20 + ->withPHIDs(array($value)) 21 + ->executeOne(); 22 + if ($file) { 23 + $object->setParameter(self::PARAMKEY_NAME, $file->getName()); 24 + } 25 + } 26 + 27 + public function getTitle() { 28 + return pht( 29 + '%s imported an ICS file.', 30 + $this->renderAuthor()); 31 + } 32 + 33 + public function validateTransactions($object, array $xactions) { 34 + $viewer = $this->getActor(); 35 + $errors = array(); 36 + 37 + $ics_type = PhabricatorCalendarICSImportEngine::ENGINETYPE; 38 + $import_type = $object->getEngine()->getImportEngineType(); 39 + if ($import_type != $ics_type) { 40 + if (!$xactions) { 41 + return $errors; 42 + } 43 + 44 + $errors[] = $this->newInvalidError( 45 + pht( 46 + 'You can not attach an ICS file to an import type other than '. 47 + 'an ICS import (type is "%s").', 48 + $import_type)); 49 + 50 + return $errors; 51 + } 52 + 53 + $new_value = $object->getParameter(self::PARAMKEY_FILE); 54 + foreach ($xactions as $xaction) { 55 + $new_value = $xaction->getNewValue(); 56 + if (!strlen($new_value)) { 57 + continue; 58 + } 59 + 60 + $file = id(new PhabricatorFileQuery()) 61 + ->setViewer($viewer) 62 + ->withPHIDs(array($new_value)) 63 + ->executeOne(); 64 + if (!$file) { 65 + $errors[] = $this->newInvalidError( 66 + pht( 67 + 'File PHID "%s" is not valid or not visible.', 68 + $new_value), 69 + $xaction); 70 + } 71 + } 72 + 73 + if (!$new_value) { 74 + $errors[] = $this->newRequiredError( 75 + pht('You must select an ".ics" file to import.')); 76 + } 77 + 78 + return $errors; 79 + } 80 + }
+39
src/applications/calendar/xaction/PhabricatorCalendarImportNameTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorCalendarImportNameTransaction 4 + extends PhabricatorCalendarImportTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'calendar.import.name'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getName(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setName($value); 14 + } 15 + 16 + public function getTitle() { 17 + $old = $this->getOldValue(); 18 + $new = $this->getNewValue(); 19 + 20 + if (!strlen($old)) { 21 + return pht( 22 + '%s named this import %s.', 23 + $this->renderAuthor(), 24 + $this->renderNewValue()); 25 + } else if (!strlen($new)) { 26 + return pht( 27 + '%s removed the name of this import (was: %s).', 28 + $this->renderAuthor(), 29 + $this->renderOldValue()); 30 + } else { 31 + return pht( 32 + '%s renamed this import from %s to %s.', 33 + $this->renderAuthor(), 34 + $this->renderOldValue(), 35 + $this->renderNewValue()); 36 + } 37 + } 38 + 39 + }
+4
src/applications/calendar/xaction/PhabricatorCalendarImportTransactionType.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorCalendarImportTransactionType 4 + extends PhabricatorModularTransactionType {}
+23
src/applications/transactions/editfield/PhabricatorFileEditField.php
··· 1 + <?php 2 + 3 + final class PhabricatorFileEditField 4 + extends PhabricatorEditField { 5 + 6 + protected function newControl() { 7 + return new PHUIFormFileControl(); 8 + } 9 + 10 + protected function newHTTPParameterType() { 11 + return new AphrontFileHTTPParameterType(); 12 + } 13 + 14 + protected function newConduitParameterType() { 15 + return new ConduitPHIDParameterType(); 16 + } 17 + 18 + public function appendToForm(AphrontFormView $form) { 19 + $form->setEncType('multipart/form-data'); 20 + return parent::appendToForm($form); 21 + } 22 + 23 + }