From 0398097498694f661e27fa4c3eff5f069ae2ec2e Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 4 Nov 2015 12:52:52 -0800 Subject: [PATCH] Allow ApplicationEditor forms to be reconfigured Summary: Ref T9132. This diff doesn't do anything interesting, it just lays the groundwork for more interesting future diffs. Broadly, the idea here is to let you create multiple views of each edit form. For example, we might create several different "Create Task" forms, like: - "New Bug Report" - "New Feature Request" These would be views of the "Create Task" form, but with various adjustments: - A form might have additional instructions ("how to file a good bug report"). - A form might have prefilled values for some fields (like particular projects, subscribers, or policies). - A form might have some fields locked (so they can not be edited) or hidden. - A form might have a different field order. - A form might have a limited visibility policy, so only some users can access it. This diff adds a new storage object (`EditEngineConfiguration`) to keep track of all those customizations and represent "a form which has been configured to look and work a certain way". This doesn't let these configurations do anything useful/interesting, and you can't access them directly yet, it's just all the boring plumbing to enable more interesting behavior in the future. Test Plan: ApplicationEditor forms now let you manage available forms and edit the current form: {F959025} There's a new (bare bones) list of all available engines: {F959030} And if you jump into an engine, you can see all the forms for it: {F959038} The actual form configurations have standard detail/edit pages. The edit pages are themselves driven by ApplicationEditor, of course, so you can edit the form for editing forms. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9132 Differential Revision: https://secure.phabricator.com/D14453 --- .../20151106.editengine.1.table.sql | 17 ++ .../20151106.editengine.2.xactions.sql | 19 ++ src/__phutil_library_map__.php | 53 +++- .../base/PhabricatorApplication.php | 6 +- .../conduit/PasteEditConduitAPIMethod.php | 2 +- .../editor/PhabricatorPasteEditEngine.php | 10 +- .../PhabricatorTransactionsApplication.php | 14 + ...rEditEngineConfigurationEditController.php | 26 ++ ...rEditEngineConfigurationListController.php | 34 +++ ...rEditEngineConfigurationSaveController.php | 55 ++++ ...rEditEngineConfigurationViewController.php | 125 +++++++++ .../PhabricatorEditEngineController.php | 37 +++ .../PhabricatorEditEngineListController.php | 16 ++ ...itEngine.php => PhabricatorEditEngine.php} | 245 +++++++++++++++++- ...php => PhabricatorEditEngineAPIMethod.php} | 2 +- .../editfield/PhabricatorEditField.php | 28 +- .../PhabricatorInstructionsEditField.php | 10 + ...habricatorApplicationTransactionEditor.php | 12 - ...catorEditEngineConfigurationEditEngine.php | 78 ++++++ ...abricatorEditEngineConfigurationEditor.php | 98 +++++++ ...ricatorEditEngineConfigurationPHIDType.php | 43 +++ ...habricatorEditEngineConfigurationQuery.php | 183 +++++++++++++ ...torEditEngineConfigurationSearchEngine.php | 102 ++++++++ ...ditEngineConfigurationTransactionQuery.php | 10 + .../query/PhabricatorEditEngineQuery.php | 31 +++ .../PhabricatorEditEngineSearchEngine.php | 78 ++++++ .../PhabricatorEditEngineConfiguration.php | 211 +++++++++++++++ ...atorEditEngineConfigurationTransaction.php | 20 ++ ...orApplicationEditHTTPParameterHelpView.php | 2 +- 29 files changed, 1536 insertions(+), 31 deletions(-) create mode 100644 resources/sql/autopatches/20151106.editengine.1.table.sql create mode 100644 resources/sql/autopatches/20151106.editengine.2.xactions.sql create mode 100644 src/applications/transactions/controller/PhabricatorEditEngineConfigurationEditController.php create mode 100644 src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php create mode 100644 src/applications/transactions/controller/PhabricatorEditEngineConfigurationSaveController.php create mode 100644 src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php create mode 100644 src/applications/transactions/controller/PhabricatorEditEngineController.php create mode 100644 src/applications/transactions/controller/PhabricatorEditEngineListController.php rename src/applications/transactions/editengine/{PhabricatorApplicationEditEngine.php => PhabricatorEditEngine.php} (74%) rename src/applications/transactions/editengine/{PhabricatorApplicationEditEngineAPIMethod.php => PhabricatorEditEngineAPIMethod.php} (98%) create mode 100644 src/applications/transactions/editfield/PhabricatorInstructionsEditField.php create mode 100644 src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php create mode 100644 src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php create mode 100644 src/applications/transactions/phid/PhabricatorEditEngineConfigurationPHIDType.php create mode 100644 src/applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php create mode 100644 src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php create mode 100644 src/applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php create mode 100644 src/applications/transactions/query/PhabricatorEditEngineQuery.php create mode 100644 src/applications/transactions/query/PhabricatorEditEngineSearchEngine.php create mode 100644 src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php create mode 100644 src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php diff --git a/resources/sql/autopatches/20151106.editengine.1.table.sql b/resources/sql/autopatches/20151106.editengine.1.table.sql new file mode 100644 index 0000000000..bda84d9443 --- /dev/null +++ b/resources/sql/autopatches/20151106.editengine.1.table.sql @@ -0,0 +1,17 @@ +CREATE TABLE {$NAMESPACE}_search.search_editengineconfiguration ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + engineKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}, + builtinKey VARCHAR(64) COLLATE {$COLLATE_TEXT}, + name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + isDisabled BOOL NOT NULL DEFAULT 0, + isDefault BOOL NOT NULL DEFAULT 0, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_engine` (engineKey, builtinKey), + KEY `key_default` (engineKey, isDefault, isDisabled) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20151106.editengine.2.xactions.sql b/resources/sql/autopatches/20151106.editengine.2.xactions.sql new file mode 100644 index 0000000000..36a9d7a769 --- /dev/null +++ b/resources/sql/autopatches/20151106.editengine.2.xactions.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_search.search_editengineconfigurationtransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b0b0c6731f..ad5809a954 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1579,8 +1579,6 @@ phutil_register_library_map(array( 'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php', 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', - 'PhabricatorApplicationEditEngine' => 'applications/transactions/editengine/PhabricatorApplicationEditEngine.php', - 'PhabricatorApplicationEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorApplicationEditEngineAPIMethod.php', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php', 'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php', 'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php', @@ -2110,6 +2108,24 @@ phutil_register_library_map(array( 'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php', 'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php', 'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php', + 'PhabricatorEditEngine' => 'applications/transactions/editengine/PhabricatorEditEngine.php', + 'PhabricatorEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php', + 'PhabricatorEditEngineConfiguration' => 'applications/transactions/storage/PhabricatorEditEngineConfiguration.php', + 'PhabricatorEditEngineConfigurationEditController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationEditController.php', + 'PhabricatorEditEngineConfigurationEditEngine' => 'applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php', + 'PhabricatorEditEngineConfigurationEditor' => 'applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php', + 'PhabricatorEditEngineConfigurationListController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php', + 'PhabricatorEditEngineConfigurationPHIDType' => 'applications/transactions/phid/PhabricatorEditEngineConfigurationPHIDType.php', + 'PhabricatorEditEngineConfigurationQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php', + 'PhabricatorEditEngineConfigurationSaveController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationSaveController.php', + 'PhabricatorEditEngineConfigurationSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php', + 'PhabricatorEditEngineConfigurationTransaction' => 'applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php', + 'PhabricatorEditEngineConfigurationTransactionQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php', + 'PhabricatorEditEngineConfigurationViewController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php', + 'PhabricatorEditEngineController' => 'applications/transactions/controller/PhabricatorEditEngineController.php', + 'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php', + 'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php', + 'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php', 'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php', 'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php', 'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php', @@ -2294,6 +2310,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentInterface' => 'infrastructure/diff/interface/PhabricatorInlineCommentInterface.php', 'PhabricatorInlineCommentPreviewController' => 'infrastructure/diff/PhabricatorInlineCommentPreviewController.php', 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', + 'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php', 'PhabricatorInternationalizationManagementWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementWorkflow.php', 'PhabricatorInvalidConfigSetupCheck' => 'applications/config/check/PhabricatorInvalidConfigSetupCheck.php', @@ -5500,7 +5517,7 @@ phutil_register_library_map(array( 'PasteCreateMailReceiver' => 'PhabricatorMailReceiver', 'PasteDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PasteDefaultViewCapability' => 'PhabricatorPolicyCapability', - 'PasteEditConduitAPIMethod' => 'PhabricatorApplicationEditEngineAPIMethod', + 'PasteEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PasteEmbedView' => 'AphrontView', 'PasteInfoConduitAPIMethod' => 'PasteConduitAPIMethod', 'PasteMailReceiver' => 'PhabricatorObjectMailReceiver', @@ -5544,8 +5561,6 @@ phutil_register_library_map(array( 'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', - 'PhabricatorApplicationEditEngine' => 'Phobject', - 'PhabricatorApplicationEditEngineAPIMethod' => 'ConduitAPIMethod', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView', 'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationLaunchView' => 'AphrontTagView', @@ -6172,6 +6187,31 @@ phutil_register_library_map(array( 'PhabricatorEdgeTestCase' => 'PhabricatorTestCase', 'PhabricatorEdgeType' => 'Phobject', 'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase', + 'PhabricatorEditEngine' => array( + 'Phobject', + 'PhabricatorPolicyInterface', + ), + 'PhabricatorEditEngineAPIMethod' => 'ConduitAPIMethod', + 'PhabricatorEditEngineConfiguration' => array( + 'PhabricatorSearchDAO', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorPolicyInterface', + ), + 'PhabricatorEditEngineConfigurationEditController' => 'PhabricatorEditEngineController', + 'PhabricatorEditEngineConfigurationEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorEditEngineConfigurationEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorEditEngineConfigurationListController' => 'PhabricatorEditEngineController', + 'PhabricatorEditEngineConfigurationPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorEditEngineConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorEditEngineConfigurationSaveController' => 'PhabricatorEditEngineController', + 'PhabricatorEditEngineConfigurationSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorEditEngineConfigurationTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorEditEngineConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorEditEngineConfigurationViewController' => 'PhabricatorEditEngineController', + 'PhabricatorEditEngineController' => 'PhabricatorApplicationTransactionController', + 'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController', + 'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorEditField' => 'Phobject', 'PhabricatorEditType' => 'Phobject', 'PhabricatorEditor' => 'Phobject', @@ -6392,6 +6432,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentInterface' => 'PhabricatorMarkupInterface', 'PhabricatorInlineCommentPreviewController' => 'PhabricatorController', 'PhabricatorInlineSummaryView' => 'AphrontView', + 'PhabricatorInstructionsEditField' => 'PhabricatorEditField', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'PhabricatorInternationalizationManagementWorkflow', 'PhabricatorInternationalizationManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorInvalidConfigSetupCheck' => 'PhabricatorSetupCheck', @@ -6710,7 +6751,7 @@ phutil_register_library_map(array( 'PhabricatorPasteController' => 'PhabricatorController', 'PhabricatorPasteDAO' => 'PhabricatorLiskDAO', 'PhabricatorPasteEditController' => 'PhabricatorPasteController', - 'PhabricatorPasteEditEngine' => 'PhabricatorApplicationEditEngine', + 'PhabricatorPasteEditEngine' => 'PhabricatorEditEngine', 'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorPasteListController' => 'PhabricatorPasteController', 'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType', diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index 080062a2d9..8dce30290d 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -635,8 +635,12 @@ abstract class PhabricatorApplication return array(); } - protected function getEditRoutePattern($base) { + protected function getEditRoutePattern($base = null) { return $base.'(?:(?P[0-9]\d*)/)?(?:(?Pparameters)/)?'; } + protected function getQueryRoutePattern($base = null) { + return $base.'(?:query/(?P[^/]+)/)?'; + } + } diff --git a/src/applications/paste/conduit/PasteEditConduitAPIMethod.php b/src/applications/paste/conduit/PasteEditConduitAPIMethod.php index c02fb940e8..cfbb8de612 100644 --- a/src/applications/paste/conduit/PasteEditConduitAPIMethod.php +++ b/src/applications/paste/conduit/PasteEditConduitAPIMethod.php @@ -1,7 +1,7 @@ getViewer()); @@ -24,7 +30,7 @@ final class PhabricatorPasteEditEngine return $object->getMonogram(); } - protected function getObjectCreateShortText($object) { + protected function getObjectCreateShortText() { return pht('Create Paste'); } diff --git a/src/applications/transactions/application/PhabricatorTransactionsApplication.php b/src/applications/transactions/application/PhabricatorTransactionsApplication.php index 1a8792b256..260fa889b4 100644 --- a/src/applications/transactions/application/PhabricatorTransactionsApplication.php +++ b/src/applications/transactions/application/PhabricatorTransactionsApplication.php @@ -33,6 +33,20 @@ final class PhabricatorTransactionsApplication extends PhabricatorApplication { => 'PhabricatorApplicationTransactionShowOlderController', '(?Pold|new)/(?[^/]+)/' => 'PhabricatorApplicationTransactionValueController', + 'editengine/' => array( + $this->getQueryRoutePattern() + => 'PhabricatorEditEngineListController', + '(?P[^/]+)/' => array( + $this->getQueryRoutePattern() => + 'PhabricatorEditEngineConfigurationListController', + $this->getEditRoutePattern('edit/') => + 'PhabricatorEditEngineConfigurationEditController', + 'view/(?P[^/]+)/' => + 'PhabricatorEditEngineConfigurationViewController', + 'save/(?P[^/]+)/' => + 'PhabricatorEditEngineConfigurationSaveController', + ), + ), ), ); } diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationEditController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationEditController.php new file mode 100644 index 0000000000..1227ebc552 --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationEditController.php @@ -0,0 +1,26 @@ +getViewer(); + + $target_engine_key = $request->getURIData('engineKey'); + + $target_engine = PhabricatorEditEngine::getByKey( + $viewer, + $target_engine_key); + if (!$target_engine) { + return new Aphront404Response(); + } + + $this->setEngineKey($target_engine->getEngineKey()); + + return id(new PhabricatorEditEngineConfigurationEditEngine()) + ->setTargetEngine($target_engine) + ->setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php new file mode 100644 index 0000000000..b2c18f13e5 --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php @@ -0,0 +1,34 @@ +setEngineKey($request->getURIData('engineKey')); + + return id(new PhabricatorEditEngineConfigurationSearchEngine()) + ->setController($this) + ->setEngineKey($this->getEngineKey()) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $engine_key = $this->getEngineKey(); + $edit_uri = "/transactions/editengine/{$engine_key}/edit/"; + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('New Form')) + ->setHref($edit_uri) + ->setIcon('fa-plus-square')); + + return $crumbs; + } + +} diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationSaveController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationSaveController.php new file mode 100644 index 0000000000..f7ca08b01a --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationSaveController.php @@ -0,0 +1,55 @@ +getURIData('engineKey'); + $this->setEngineKey($engine_key); + + $key = $request->getURIData('key'); + $viewer = $this->getViewer(); + + $config = id(new PhabricatorEditEngineConfigurationQuery()) + ->setViewer($viewer) + ->withEngineKeys(array($engine_key)) + ->withIdentifiers(array($key)) + ->executeOne(); + if (!$config) { + return id(new Aphront404Response()); + } + + $view_uri = $config->getURI(); + + if ($config->getID()) { + return $this->newDialog() + ->setTitle(pht('Already Editable')) + ->appendParagraph( + pht('This form configuration is already editable.')) + ->addCancelButton($view_uri); + } + + if ($request->isFormPost()) { + $editor = id(new PhabricatorEditEngineConfigurationEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($config, array()); + + return id(new AphrontRedirectResponse()) + ->setURI($config->getURI()); + } + + // TODO: Explain what this means in more detail once the implications are + // more clear, or just link to some docs or something. + + return $this->newDialog() + ->setTitle(pht('Make Builtin Editable')) + ->appendParagraph( + pht('Make this builtin form editable?')) + ->addSubmitButton(pht('Make Editable')) + ->addCancelButton($view_uri); + } + +} diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php new file mode 100644 index 0000000000..f65eed1746 --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php @@ -0,0 +1,125 @@ +getURIData('engineKey'); + $this->setEngineKey($engine_key); + + $key = $request->getURIData('key'); + $viewer = $this->getViewer(); + + $config = id(new PhabricatorEditEngineConfigurationQuery()) + ->setViewer($viewer) + ->withEngineKeys(array($engine_key)) + ->withIdentifiers(array($key)) + ->executeOne(); + if (!$config) { + return id(new Aphront404Response()); + } + + $is_concrete = (bool)$config->getID(); + + $actions = $this->buildActionView($config); + + $properties = $this->buildPropertyView($config) + ->setActionList($actions); + + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setPolicyObject($config) + ->setHeader(pht('Edit Form: %s', $config->getDisplayName())); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($properties); + + $crumbs = $this->buildApplicationCrumbs(); + + if ($is_concrete) { + $crumbs->addTextCrumb(pht('Form %d', $config->getID())); + } else { + $crumbs->addTextCrumb(pht('Builtin')); + } + + if ($is_concrete) { + $timeline = $this->buildTransactionTimeline( + $config, + new PhabricatorEditEngineConfigurationTransactionQuery()); + + $timeline->setShouldTerminate(true); + } else { + $timeline = null; + } + + return $this->newPage() + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, + $timeline, + )); + } + + private function buildActionView( + PhabricatorEditEngineConfiguration $config) { + $viewer = $this->getViewer(); + $engine_key = $this->getEngineKey(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $config, + PhabricatorPolicyCapability::CAN_EDIT); + + $view = id(new PhabricatorActionListView()) + ->setUser($viewer); + + $key = $config->getIdentifier(); + + $base_uri = "/transactions/editengine/{$engine_key}"; + + $is_concrete = (bool)$config->getID(); + if (!$is_concrete) { + $save_uri = "{$base_uri}/save/{$key}/"; + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Make Editable')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(true) + ->setHref($save_uri)); + + $can_edit = false; + } else { + $edit_uri = "{$base_uri}/edit/{$key}/"; + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Form Configuration')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)); + } + + return $view; + } + + private function buildPropertyView( + PhabricatorEditEngineConfiguration $config) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($config); + + return $properties; + } + + +} diff --git a/src/applications/transactions/controller/PhabricatorEditEngineController.php b/src/applications/transactions/controller/PhabricatorEditEngineController.php new file mode 100644 index 0000000000..e920260f37 --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorEditEngineController.php @@ -0,0 +1,37 @@ +engineKey = $engine_key; + return $this; + } + + public function getEngineKey() { + return $this->engineKey; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $crumbs->addTextCrumb(pht('Edit Engines'), '/transactions/editengine/'); + + $engine_key = $this->getEngineKey(); + if ($engine_key !== null) { + $engine = PhabricatorEditEngine::getByKey( + $this->getViewer(), + $engine_key); + if ($engine) { + $crumbs->addTextCrumb( + $engine->getEngineName(), + "/transactions/editengine/{$engine_key}/"); + } + } + + return $crumbs; + } + +} diff --git a/src/applications/transactions/controller/PhabricatorEditEngineListController.php b/src/applications/transactions/controller/PhabricatorEditEngineListController.php new file mode 100644 index 0000000000..3120d6846c --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorEditEngineListController.php @@ -0,0 +1,16 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php similarity index 74% rename from src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php rename to src/applications/transactions/editengine/PhabricatorEditEngine.php index 86a03b51f4..3fca734330 100644 --- a/src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -4,6 +4,7 @@ /** * @task fields Managing Fields * @task text Display Text + * @task config Edit Engine Configuration * @task uri Managing URIs * @task load Creating and Loading Objects * @task web Responding to Web Requests @@ -11,11 +12,16 @@ * @task http Responding to HTTP Parameter Requests * @task conduit Responding to Conduit Requests */ -abstract class PhabricatorApplicationEditEngine extends Phobject { +abstract class PhabricatorEditEngine + extends Phobject + implements PhabricatorPolicyInterface { + + const EDITENGINECONFIG_DEFAULT = 'default'; private $viewer; private $controller; private $isCreate; + private $editEngineConfiguration; final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -36,6 +42,10 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { return $this->controller; } + final public function getEngineKey() { + return $this->getPhobjectClassConstant('ENGINECONST', 64); + } + /* -( Managing Fields )---------------------------------------------------- */ @@ -184,6 +194,9 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { } } + $config = $this->getEditEngineConfiguration(); + $fields = $config->applyConfigurationToFields($this, $fields); + foreach ($fields as $field) { $field ->setViewer($viewer) @@ -197,6 +210,12 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { /* -( Display Text )------------------------------------------------------- */ + /** + * @task text + */ + abstract public function getEngineName(); + + /** * @task text */ @@ -212,7 +231,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { /** * @task text */ - abstract protected function getObjectCreateShortText($object); + abstract protected function getObjectCreateShortText(); /** @@ -237,6 +256,121 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { } +/* -( Edit Engine Configuration )------------------------------------------ */ + + + protected function supportsEditEngineConfiguration() { + return true; + } + + final protected function getEditEngineConfiguration() { + return $this->editEngineConfiguration; + } + + private function loadEditEngineConfiguration($key) { + if ($key === null) { + $key = self::EDITENGINECONFIG_DEFAULT; + } + + $config = id(new PhabricatorEditEngineConfigurationQuery()) + ->setViewer($this->getViewer()) + ->withEngineKeys(array($this->getEngineKey())) + ->withIdentifiers(array($key)) + ->executeOne(); + if (!$config) { + return null; + } + + $this->editEngineConfiguration = $config; + + return $config; + } + + final public function getBuiltinEngineConfigurations() { + $configurations = $this->newBuiltinEngineConfigurations(); + + if (!$configurations) { + throw new Exception( + pht( + 'EditEngine ("%s") returned no builtin engine configurations, but '. + 'an edit engine must have at least one configuration.', + get_class($this))); + } + + assert_instances_of($configurations, 'PhabricatorEditEngineConfiguration'); + + $has_default = false; + foreach ($configurations as $config) { + if ($config->getBuiltinKey() == self::EDITENGINECONFIG_DEFAULT) { + $has_default = true; + } + } + + if (!$has_default) { + $first = head($configurations); + if (!$first->getBuiltinKey()) { + $first->setBuiltinKey(self::EDITENGINECONFIG_DEFAULT); + + if (!strlen($first->getName())) { + $first->setName($this->getObjectCreateShortText()); + } + } else { + throw new Exception( + pht( + 'EditEngine ("%s") returned builtin engine configurations, '. + 'but none are marked as default and the first configuration has '. + 'a different builtin key already. Mark a builtin as default or '. + 'omit the key from the first configuration', + get_class($this))); + } + } + + $builtins = array(); + foreach ($configurations as $key => $config) { + $builtin_key = $config->getBuiltinKey(); + + if ($builtin_key === null) { + throw new Exception( + pht( + 'EditEngine ("%s") returned builtin engine configurations, '. + 'but one (with key "%s") is missing a builtin key. Provide a '. + 'builtin key for each configuration (you can omit it from the '. + 'first configuration in the list to automatically assign the '. + 'default key).', + get_class($this), + $key)); + } + + if (isset($builtins[$builtin_key])) { + throw new Exception( + pht( + 'EditEngine ("%s") returned builtin engine configurations, '. + 'but at least two specify the same builtin key ("%s"). Engines '. + 'must have unique builtin keys.', + get_class($this), + $builtin_key)); + } + + $builtins[$builtin_key] = $config; + } + + + return $builtins; + } + + protected function newBuiltinEngineConfigurations() { + return array( + $this->newConfiguration(), + ); + } + + final protected function newConfiguration() { + return PhabricatorEditEngineConfiguration::initializeNewConfiguration( + $this->getViewer(), + $this); + } + + /* -( Managing URIs )------------------------------------------------------ */ @@ -317,7 +451,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { * @return bool True if a new object is being created. * @task load */ - final protected function getIsCreate() { + final public function getIsCreate() { return $this->isCreate; } @@ -391,6 +525,35 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { } + /** + * Verify that an object is appropriate for editing. + * + * @param wild Loaded value. + * @return void + * @task load + */ + private function validateObject($object) { + if (!$object || !is_object($object)) { + throw new Exception( + pht( + 'EditEngine "%s" created or loaded an invalid object: object must '. + 'actually be an object, but is of some other type ("%s").', + get_class($this), + gettype($object))); + } + + if (!($object instanceof PhabricatorApplicationTransactionInterface)) { + throw new Exception( + pht( + 'EditEngine "%s" created or loaded an invalid object: object (of '. + 'class "%s") must implement "%s", but does not.', + get_class($this), + get_class($object), + 'PhabricatorApplicationTransactionInterface')); + } + } + + /* -( Responding to Web Requests )----------------------------------------- */ @@ -399,6 +562,11 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { $controller = $this->getController(); $request = $controller->getRequest(); + $config = $this->loadEditEngineConfiguration($request->getURIData('form')); + if (!$config) { + return new Aphront404Response(); + } + $id = $request->getURIData('id'); if ($id) { $this->setIsCreate(false); @@ -411,6 +579,8 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { $object = $this->newEditableObject(); } + $this->validateObject($object); + $action = $request->getURIData('editAction'); switch ($action) { case 'parameters': @@ -425,7 +595,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { $crumbs = $controller->buildApplicationCrumbsForEditEngine(); if ($this->getIsCreate()) { - $create_text = $this->getObjectCreateShortText($object); + $create_text = $this->getObjectCreateShortText(); if ($final) { $crumbs->addTextCrumb($create_text); } else { @@ -570,6 +740,20 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { private function buildEditFormActions($object) { $actions = array(); + if ($this->supportsEditEngineConfiguration()) { + $engine_key = $this->getEngineKey(); + $config = $this->getEditEngineConfiguration(); + + $actions[] = id(new PhabricatorActionView()) + ->setName(pht('Manage Form Configurations')) + ->setIcon('fa-list-ul') + ->setHref("/transactions/editengine/{$engine_key}/"); + $actions[] = id(new PhabricatorActionView()) + ->setName(pht('Edit Form Configuration')) + ->setIcon('fa-pencil') + ->setHref($config->getURI()); + } + $actions[] = id(new PhabricatorActionView()) ->setName(pht('Show HTTP Parameters')) ->setIcon('fa-crosshairs') @@ -601,7 +785,7 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { $header_text = pht( 'HTTP Parameters: %s', - $this->getObjectCreateShortText($object)); + $this->getObjectCreateShortText()); $header = id(new PHUIHeaderView()) ->setHeader($header_text); @@ -637,6 +821,14 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { final public function buildConduitResponse(ConduitAPIRequest $request) { $viewer = $this->getViewer(); + $config = $this->loadEditEngineConfiguration(null); + if (!$config) { + throw new Exception( + pht( + 'Unable to load configuration for this EditEngine ("%s").', + get_class($this))); + } + $phid = $request->getValue('objectPHID'); if ($phid) { $this->setIsCreate(false); @@ -649,6 +841,8 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { $object = $this->newEditableObject(); } + $this->validateObject($object); + $fields = $this->buildEditFields($object); $types = $this->getAllEditTypesFromFields($fields); @@ -772,5 +966,46 @@ abstract class PhabricatorApplicationEditEngine extends Phobject { return $this->getAllEditTypesFromFields($fields); } + final public static function getAllEditEngines() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getEngineKey') + ->execute(); + } + final public static function getByKey(PhabricatorUser $viewer, $key) { + return id(new PhabricatorEditEngineQuery()) + ->setViewer($viewer) + ->withEngineKeys(array($key)) + ->executeOne(); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getPHID() { + return get_class($this); + } + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } } diff --git a/src/applications/transactions/editengine/PhabricatorApplicationEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php similarity index 98% rename from src/applications/transactions/editengine/PhabricatorApplicationEditEngineAPIMethod.php rename to src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php index 9fc6495455..dafc56c825 100644 --- a/src/applications/transactions/editengine/PhabricatorApplicationEditEngineAPIMethod.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php @@ -1,6 +1,6 @@ key = $key; @@ -69,7 +69,18 @@ abstract class PhabricatorEditField extends Phobject { return $this->description; } - abstract protected function newControl(); + public function setIsLocked($is_locked) { + $this->isLocked = $is_locked; + return $this; + } + + public function getIsLocked() { + return $this->isLocked; + } + + protected function newControl() { + throw new PhutilMethodNotImplementedException(); + } protected function renderControl() { $control = $this->newControl(); @@ -85,6 +96,10 @@ abstract class PhabricatorEditField extends Phobject { $control->setLabel($this->getLabel()); } + if ($this->getIsLocked()) { + $control->setDisabled(true); + } + return $control; } @@ -166,6 +181,15 @@ abstract class PhabricatorEditField extends Phobject { return $this; } + public function readDefaultValueFromConfiguration($value) { + $this->value = $this->getDefaultValueFromConfiguration($value); + return $this; + } + + protected function getDefaultValueFromConfiguration($value) { + return $value; + } + protected function getValueFromObject($object) { if ($this->hasValue) { return $this->value; diff --git a/src/applications/transactions/editfield/PhabricatorInstructionsEditField.php b/src/applications/transactions/editfield/PhabricatorInstructionsEditField.php new file mode 100644 index 0000000000..9da1d49ae6 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorInstructionsEditField.php @@ -0,0 +1,10 @@ +appendRemarkupInstructions($this->getValue()); + } + +} diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index a83ea2ba22..1f390ed468 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -815,18 +815,6 @@ abstract class PhabricatorApplicationTransactionEditor $xactions = $this->filterTransactions($object, $xactions); - if (!$xactions) { - if ($read_locking) { - $object->endReadLocking(); - $read_locking = false; - } - if ($transaction_open) { - $object->killTransaction(); - $transaction_open = false; - } - return array(); - } - // Now that we've merged, filtered, and combined transactions, check for // required capabilities. foreach ($xactions as $xaction) { diff --git a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php new file mode 100644 index 0000000000..17b604b7b2 --- /dev/null +++ b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php @@ -0,0 +1,78 @@ +targetEngine = $target_engine; + return $this; + } + + public function getTargetEngine() { + return $this->targetEngine; + } + + public function getEngineName() { + return pht('Edit Configurations'); + } + + protected function newEditableObject() { + return PhabricatorEditEngineConfiguration::initializeNewConfiguration( + $this->getViewer(), + $this->getTargetEngine()); + } + + protected function newObjectQuery() { + return id(new PhabricatorEditEngineConfigurationQuery()); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Form'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Form %d: %s', $object->getID(), $object->getDisplayName()); + } + + protected function getObjectEditShortText($object) { + return pht('Form %d', $object->getID()); + } + + protected function getObjectCreateShortText() { + return pht('Create Form'); + } + + protected function getObjectViewURI($object) { + $engine_key = $this->getTargetEngine()->getEngineKey(); + $id = $object->getID(); + return "/transactions/editengine/{$engine_key}/view/{$id}/"; + } + + protected function getObjectEditURI($object) { + $engine_key = $this->getTargetEngine()->getEngineKey(); + $id = $object->getID(); + return "/transactions/editengine/{$engine_key}/edit/{$id}/"; + } + + protected function getObjectCreateCancelURI($object) { + $engine_key = $this->getTargetEngine()->getEngineKey(); + return "/transactions/editengine/{$engine_key}/"; + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the form.')) + ->setTransactionType( + PhabricatorEditEngineConfigurationTransaction::TYPE_NAME) + ->setValue($object->getName()), + ); + } + +} diff --git a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php new file mode 100644 index 0000000000..4736e89efa --- /dev/null +++ b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php @@ -0,0 +1,98 @@ +validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Form name is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + + protected function getCustomTransactionOldValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorEditEngineConfigurationTransaction::TYPE_NAME: + return $object->getName(); + } + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorEditEngineConfigurationTransaction::TYPE_NAME: + return $xaction->getNewValue(); + } + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorEditEngineConfigurationTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorEditEngineConfigurationTransaction::TYPE_NAME: + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + +} diff --git a/src/applications/transactions/phid/PhabricatorEditEngineConfigurationPHIDType.php b/src/applications/transactions/phid/PhabricatorEditEngineConfigurationPHIDType.php new file mode 100644 index 0000000000..b4ac0ddbfe --- /dev/null +++ b/src/applications/transactions/phid/PhabricatorEditEngineConfigurationPHIDType.php @@ -0,0 +1,43 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $config = $objects[$phid]; + + $id = $config->getID(); + $name = $config->getName(); + + $handle->setName($name); + $handle->setURI($config->getURI()); + } + } + +} diff --git a/src/applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php b/src/applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php new file mode 100644 index 0000000000..b1c573f775 --- /dev/null +++ b/src/applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php @@ -0,0 +1,183 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withEngineKeys(array $engine_keys) { + $this->engineKeys = $engine_keys; + return $this; + } + + public function withBuiltinKeys(array $builtin_keys) { + $this->builtinKeys = $builtin_keys; + return $this; + } + + public function withIdentifiers(array $identifiers) { + $this->identifiers = $identifiers; + return $this; + } + + public function newResultObject() { + return new PhabricatorEditEngineConfiguration(); + } + + protected function loadPage() { + // TODO: The logic here is a little flimsy and won't survive pagination. + // For now, I'm just not bothering with pagination since I believe it will + // take some time before any install manages to produce a large enough + // number of edit forms for any particular engine for the lack of UI + // pagination to become a problem. + + $page = $this->loadStandardPage($this->newResultObject()); + + // Now that we've loaded the real results from the database, we're going + // to load builtins from the edit engines and add them to the list. + + $engines = PhabricatorEditEngine::getAllEditEngines(); + + if ($this->engineKeys) { + $engines = array_select_keys($engines, $this->engineKeys); + } + + foreach ($engines as $engine) { + $engine->setViewer($this->getViewer()); + } + + // List all the builtins which have already been saved to the database as + // real objects. + $concrete = array(); + foreach ($page as $config) { + $builtin_key = $config->getBuiltinKey(); + if ($builtin_key !== null) { + $engine_key = $config->getEngineKey(); + $concrete[$engine_key][$builtin_key] = $config; + } + } + + $builtins = array(); + foreach ($engines as $engine_key => $engine) { + $engine_builtins = $engine->getBuiltinEngineConfigurations(); + foreach ($engine_builtins as $engine_builtin) { + $builtin_key = $engine_builtin->getBuiltinKey(); + if (isset($concrete[$engine_key][$builtin_key])) { + continue; + } else { + $builtins[] = $engine_builtin; + } + } + } + + foreach ($builtins as $builtin) { + $page[] = $builtin; + } + + // Now we have to do some extra filtering to make sure everything we're + // about to return really satisfies the query. + + if ($this->ids !== null) { + $ids = array_fuse($this->ids); + foreach ($page as $key => $config) { + if (empty($ids[$config->getID()])) { + unset($page[$key]); + } + } + } + + if ($this->phids !== null) { + $phids = array_fuse($this->phids); + foreach ($page as $key => $config) { + if (empty($phids[$config->getPHID()])) { + unset($page[$key]); + } + } + } + + if ($this->builtinKeys !== null) { + $builtin_keys = array_fuse($this->builtinKeys); + foreach ($page as $key => $config) { + if (empty($builtin_keys[$config->getBuiltinKey()])) { + unset($page[$key]); + } + } + } + + if ($this->identifiers !== null) { + $identifiers = array_fuse($this->identifiers); + foreach ($page as $key => $config) { + if (isset($identifiers[$config->getBuiltinKey()])) { + continue; + } + if (isset($identifiers[$config->getID()])) { + continue; + } + unset($page[$key]); + } + } + + return $page; + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->engineKeys !== null) { + $where[] = qsprintf( + $conn, + 'engineKey IN (%Ls)', + $this->engineKeys); + } + + if ($this->builtinKeys !== null) { + $where[] = qsprintf( + $conn, + 'builtinKey IN (%Ls)', + $this->builtinKeys); + } + + if ($this->identifiers !== null) { + $where[] = qsprintf( + $conn, + '(id IN (%Ls) OR builtinKey IN (%Ls))', + $this->identifiers, + $this->identifiers); + } + + return $where; + } + + public function getQueryApplicationClass() { + return 'PhabricatorTransactionsApplication'; + } + +} diff --git a/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php b/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php new file mode 100644 index 0000000000..8d55aeb5ac --- /dev/null +++ b/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php @@ -0,0 +1,102 @@ +engineKey = $engine_key; + return $this; + } + + public function getEngineKey() { + return $this->engineKey; + } + + public function canUseInPanelContext() { + return false; + } + + public function getResultTypeDescription() { + return pht('Forms'); + } + + public function getApplicationClassName() { + return 'PhabricatorTransactionsApplication'; + } + + public function newQuery() { + return id(new PhabricatorEditEngineConfigurationQuery()) + ->withEngineKeys(array($this->getEngineKey())); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + return $query; + } + + protected function buildCustomSearchFields() { + return array(); + } + + protected function getDefaultFieldOrder() { + return array(); + } + + protected function getURI($path) { + return '/transactions/editengine/'.$this->getEngineKey().'/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Forms'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $configs, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($configs, 'PhabricatorEditEngineConfiguration'); + $viewer = $this->requireViewer(); + $engine_key = $this->getEngineKey(); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + foreach ($configs as $config) { + $item = id(new PHUIObjectItemView()) + ->setHeader($config->getDisplayName()); + + $id = $config->getID(); + if ($id) { + $item->setObjectName(pht('Form %d', $id)); + $key = $id; + } else { + $item->setObjectName(pht('Builtin')); + $key = $config->getBuiltinKey(); + } + $item->setHref("/transactions/editengine/{$engine_key}/view/{$key}/"); + + + $list->addItem($item); + } + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list); + } +} diff --git a/src/applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php b/src/applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php new file mode 100644 index 0000000000..2a4677944e --- /dev/null +++ b/src/applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php @@ -0,0 +1,10 @@ +engineKeys = $keys; + return $this; + } + + protected function loadPage() { + $engines = PhabricatorEditEngine::getAllEditEngines(); + + if ($this->engineKeys !== null) { + $engines = array_select_keys($engines, $this->engineKeys); + } + + return $engines; + } + + public function getQueryApplicationClass() { + return 'PhabricatorTransactionsApplication'; + } + + protected function getResultCursor($object) { + return null; + } + +} diff --git a/src/applications/transactions/query/PhabricatorEditEngineSearchEngine.php b/src/applications/transactions/query/PhabricatorEditEngineSearchEngine.php new file mode 100644 index 0000000000..7b86d34bd9 --- /dev/null +++ b/src/applications/transactions/query/PhabricatorEditEngineSearchEngine.php @@ -0,0 +1,78 @@ +newQuery(); + return $query; + } + + protected function buildCustomSearchFields() { + return array(); + } + + protected function getDefaultFieldOrder() { + return array(); + } + + protected function getURI($path) { + return '/transactions/editengine/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Edit Engines'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $engines, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($engines, 'PhabricatorEditEngine'); + $viewer = $this->requireViewer(); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + foreach ($engines as $engine) { + $engine_key = $engine->getEngineKey(); + $query_uri = "/transactions/editengine/{$engine_key}/"; + + $item = id(new PHUIObjectItemView()) + ->setHeader($engine->getEngineName()) + ->setHref($query_uri); + + $list->addItem($item); + } + + return id(new PhabricatorApplicationSearchResultView()) + ->setObjectList($list); + } +} diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php new file mode 100644 index 0000000000..aed81bd8a3 --- /dev/null +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php @@ -0,0 +1,211 @@ +setEngineKey($engine->getEngineKey()) + ->attachEngine($engine) + ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) + ->setEditPolicy($edit_policy); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorEditEngineConfigurationPHIDType::TYPECONST); + } + + protected function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'engineKey' => 'text64', + 'builtinKey' => 'text64?', + 'name' => 'text255', + 'isDisabled' => 'bool', + 'isDefault' => 'bool', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_engine' => array( + 'columns' => array('engineKey', 'builtinKey'), + 'unique' => true, + ), + 'key_default' => array( + 'columns' => array('engineKey', 'isDefault', 'isDisabled'), + ), + ), + ) + parent::getConfiguration(); + } + + public function getProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function attachEngine(PhabricatorEditEngine $engine) { + $this->engine = $engine; + return $this; + } + + public function getEngine() { + return $this->assertAttached($this->engine); + } + + public function applyConfigurationToFields( + PhabricatorEditEngine $engine, + array $fields) { + $fields = mpull($fields, null, 'getKey'); + + $values = $this->getProperty('defaults', array()); + foreach ($fields as $key => $field) { + if ($engine->getIsCreate()) { + if (array_key_exists($key, $values)) { + $field->readDefaultValueFromConfiguration($values[$key]); + } + } + } + + $fields = $this->reorderFields($fields); + + $head_instructions = $this->getProperty('instructions.head'); + if (strlen($head_instructions)) { + $fields = array( + 'config.instructions.head' => id(new PhabricatorInstructionsEditField()) + ->setKey('config.instructions.head') + ->setValue($head_instructions), + ) + $fields; + } + + return $fields; + } + + private function reorderFields(array $fields) { + $keys = array(); + $fields = array_select_keys($fields, $keys) + $fields; + + // Now, move locked fields to the bottom. + $head = array(); + $tail = array(); + foreach ($fields as $key => $field) { + if (!$field->getIsLocked()) { + $head[$key] = $field; + } else { + $tail[$key] = $field; + } + } + + return $head + $tail; + } + + public function getURI() { + $engine_key = $this->getEngineKey(); + $key = $this->getIdentifier(); + + return "/transactions/editengine/{$engine_key}/view/{$key}/"; + } + + public function getIdentifier() { + $key = $this->getID(); + if (!$key) { + $key = $this->getBuiltinKey(); + } + return $key; + } + + public function getDisplayName() { + $name = $this->getName(); + if (strlen($name)) { + return $name; + } + + $builtin = $this->getBuiltinKey(); + if ($builtin !== null) { + return pht('Builtin Form "%s"', $builtin); + } + + return pht('Untitled Form'); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return $this->getEditPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorEditEngineConfigurationEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorEditEngineConfigurationTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + +} diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php new file mode 100644 index 0000000000..cbc7d2b911 --- /dev/null +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php @@ -0,0 +1,20 @@ +