1
0
Fork 0
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:
epriestley 2016-10-12 11:31:21 -07:00
parent 2ab07ed29b
commit 86a00ee4ab
19 changed files with 888 additions and 6 deletions

View file

@ -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',

View file

@ -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',
);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}
}

View file

@ -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) {

View file

@ -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';
}

View file

@ -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()) {

View file

@ -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();
}

View file

@ -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 )----------------------------------------- */

View file

@ -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());
}
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorCalendarImportTransactionType
extends PhabricatorModularTransactionType {}

View file

@ -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);
}
}