mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-21 13:00:56 +01:00
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
This commit is contained in:
parent
2ab07ed29b
commit
86a00ee4ab
19 changed files with 888 additions and 6 deletions
|
@ -140,6 +140,7 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'AphrontEpochHTTPParameterType' => 'AphrontHTTPParameterType',
|
||||
'AphrontException' => 'Exception',
|
||||
'AphrontFileHTTPParameterType' => 'AphrontHTTPParameterType',
|
||||
'AphrontFileResponse' => 'AphrontResponse',
|
||||
'AphrontFormCheckboxControl' => 'AphrontFormControl',
|
||||
'AphrontFormControl' => 'AphrontView',
|
||||
|
@ -6886,6 +6898,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
|
||||
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorCalendarICSImportEngine' => 'PhabricatorCalendarImportEngine',
|
||||
'PhabricatorCalendarICSWriter' => 'Phobject',
|
||||
'PhabricatorCalendarIconSet' => 'PhabricatorIconSet',
|
||||
'PhabricatorCalendarImport' => array(
|
||||
|
@ -6894,13 +6907,21 @@ phutil_register_library_map(array(
|
|||
'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 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFileDeleteController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileEditController' => 'PhabricatorFileController',
|
||||
'PhabricatorFileEditField' => 'PhabricatorEditField',
|
||||
'PhabricatorFileEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorFileExternalRequest' => array(
|
||||
'PhabricatorFileDAO',
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
final class AphrontFileHTTPParameterType
|
||||
extends AphrontHTTPParameterType {
|
||||
|
||||
private function getFileKey($key) {
|
||||
return $key.'_raw';
|
||||
}
|
||||
|
||||
protected function getParameterExists(AphrontRequest $request, $key) {
|
||||
$file_key = $this->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',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarImportEditController
|
||||
extends PhabricatorCalendarController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$engine = id(new PhabricatorCalendarImportEditEngine())
|
||||
->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);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,4 +9,17 @@ final class PhabricatorCalendarImportListController
|
|||
->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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarImportViewController
|
||||
extends PhabricatorCalendarController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarImportEditEngine
|
||||
extends PhabricatorEditEngine {
|
||||
|
||||
const ENGINECONST = 'calendar.import';
|
||||
|
||||
private $importEngine;
|
||||
|
||||
public function setImportEngine(PhabricatorCalendarImportEngine $engine) {
|
||||
$this->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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -15,4 +15,28 @@ final class PhabricatorCalendarImportEditor
|
|||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarICSImportEngine
|
||||
extends PhabricatorCalendarImportEngine {
|
||||
|
||||
const ENGINETYPE = 'ics';
|
||||
|
||||
public function getImportEngineName() {
|
||||
return pht('Import .ics File');
|
||||
}
|
||||
|
||||
public function getImportEngineHint() {
|
||||
return pht('Import an event in ".ics" (iCalendar) format.');
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
$parser = id(new PhutilICSParser());
|
||||
|
||||
$document = $parser->parseICSData($data);
|
||||
|
||||
return $this->importEventDocument($viewer, $import, $document);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorCalendarImportEngine
|
||||
extends Phobject {
|
||||
|
||||
final public function getImportEngineType() {
|
||||
return $this->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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -468,9 +468,9 @@ final class PhabricatorCalendarEventQuery
|
|||
->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) {
|
||||
|
|
|
@ -70,6 +70,24 @@ final class PhabricatorCalendarImportQuery
|
|||
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';
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ final class PhabricatorCalendarImportSearchEngine
|
|||
$item = id(new PHUIObjectItemView())
|
||||
->setViewer($viewer)
|
||||
->setObjectName(pht('Import %d', $import->getID()))
|
||||
->setHeader($import->getName())
|
||||
->setHeader($import->getDisplayName())
|
||||
->setHref($import->getURI());
|
||||
|
||||
if ($import->getIsDisabled()) {
|
||||
|
|
|
@ -81,6 +81,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
$datetime_end = $datetime_start->newRelativeDateTime('PT1H');
|
||||
|
||||
return id(new PhabricatorCalendarEvent())
|
||||
->setDescription('')
|
||||
->setHostPHID($actor->getPHID())
|
||||
->setIsCancelled(0)
|
||||
->setIsAllDay(0)
|
||||
|
@ -101,6 +102,66 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->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 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
return $this->getViewPolicy();
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
if ($this->getImportSource()) {
|
||||
return PhabricatorPolicy::POLICY_NOONE;
|
||||
return PhabricatorPolicies::POLICY_NOONE;
|
||||
} else {
|
||||
return $this->getEditPolicy();
|
||||
}
|
||||
|
|
|
@ -17,12 +17,16 @@ final class PhabricatorCalendarImport
|
|||
|
||||
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 @@ final class PhabricatorCalendarImport
|
|||
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 )----------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarImportDisableTransaction
|
||||
extends PhabricatorCalendarImportTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'calendar.import.disable';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return (int)$object->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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarImportICSFileTransaction
|
||||
extends PhabricatorCalendarImportTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'calendar.import.ics.file';
|
||||
const PARAMKEY_FILE = 'ics.filePHID';
|
||||
const PARAMKEY_NAME = 'ics.fileName';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarImportNameTransaction
|
||||
extends PhabricatorCalendarImportTransactionType {
|
||||
|
||||
const TRANSACTIONTYPE = 'calendar.import.name';
|
||||
|
||||
public function generateOldValue($object) {
|
||||
return $object->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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorCalendarImportTransactionType
|
||||
extends PhabricatorModularTransactionType {}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFileEditField
|
||||
extends PhabricatorEditField {
|
||||
|
||||
protected function newControl() {
|
||||
return new PHUIFormFileControl();
|
||||
}
|
||||
|
||||
protected function newHTTPParameterType() {
|
||||
return new AphrontFileHTTPParameterType();
|
||||
}
|
||||
|
||||
protected function newConduitParameterType() {
|
||||
return new ConduitPHIDParameterType();
|
||||
}
|
||||
|
||||
public function appendToForm(AphrontFormView $form) {
|
||||
$form->setEncType('multipart/form-data');
|
||||
return parent::appendToForm($form);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue