mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-18 04:42:40 +01:00
Allow Almanac properties to be deleted, use EditEngine instead of CustomField
Summary: Fixes T10410. Immediate impact of this is that you can now actually delete properties from Almanac services, devices and bindings. The meat of the change is switching from CustomField to EditEngine for most of the actual editing logic. CustomField creates a lot of problems with using EditEngine for everything else (D15326), and weird, hard-to-resolve bugs like this one (not being able to delete stuff). Using EditEngine to do this stuff instead seems like it works out much better -- I did this in ProfilePanel first and am happy with how it looks. This also makes the internal storage for properties JSON instead of raw text. Test Plan: - Created, edited and deleted properties on services, devices and bindings. - Edited and reset builtin properties on repository services. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10410 Differential Revision: https://secure.phabricator.com/D15327
This commit is contained in:
parent
411331469a
commit
ab86523ac4
32 changed files with 605 additions and 498 deletions
28
resources/sql/autopatches/20160222.almanac.1.properties.php
Normal file
28
resources/sql/autopatches/20160222.almanac.1.properties.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
$table = new AlmanacProperty();
|
||||
$conn_w = $table->establishConnection('w');
|
||||
|
||||
// We're going to JSON-encode the value in each row: previously rows stored
|
||||
// plain strings, but now they store JSON, so we need to update them.
|
||||
|
||||
foreach (new LiskMigrationIterator($table) as $property) {
|
||||
$key = $property->getFieldName();
|
||||
|
||||
$current_row = queryfx_one(
|
||||
$conn_w,
|
||||
'SELECT fieldValue FROM %T WHERE id = %d',
|
||||
$table->getTableName(),
|
||||
$property->getID());
|
||||
|
||||
if (!$current_row) {
|
||||
continue;
|
||||
}
|
||||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'UPDATE %T SET fieldValue = %s WHERE id = %d',
|
||||
$table->getTableName(),
|
||||
phutil_json_encode($current_row['fieldValue']),
|
||||
$property->getID());
|
||||
}
|
|
@ -14,6 +14,7 @@ phutil_register_library_map(array(
|
|||
'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php',
|
||||
'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php',
|
||||
'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php',
|
||||
'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php',
|
||||
'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php',
|
||||
'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php',
|
||||
'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php',
|
||||
|
@ -25,13 +26,11 @@ phutil_register_library_map(array(
|
|||
'AlmanacConduitAPIMethod' => 'applications/almanac/conduit/AlmanacConduitAPIMethod.php',
|
||||
'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php',
|
||||
'AlmanacController' => 'applications/almanac/controller/AlmanacController.php',
|
||||
'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php',
|
||||
'AlmanacCreateClusterServicesCapability' => 'applications/almanac/capability/AlmanacCreateClusterServicesCapability.php',
|
||||
'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php',
|
||||
'AlmanacCreateNamespacesCapability' => 'applications/almanac/capability/AlmanacCreateNamespacesCapability.php',
|
||||
'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php',
|
||||
'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php',
|
||||
'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php',
|
||||
'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php',
|
||||
'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php',
|
||||
'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php',
|
||||
|
@ -41,12 +40,14 @@ phutil_register_library_map(array(
|
|||
'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php',
|
||||
'AlmanacDeviceNameNgrams' => 'applications/almanac/storage/AlmanacDeviceNameNgrams.php',
|
||||
'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php',
|
||||
'AlmanacDevicePropertyEditEngine' => 'applications/almanac/editor/AlmanacDevicePropertyEditEngine.php',
|
||||
'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php',
|
||||
'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php',
|
||||
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
|
||||
'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php',
|
||||
'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php',
|
||||
'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php',
|
||||
'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php',
|
||||
'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php',
|
||||
'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php',
|
||||
'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php',
|
||||
|
@ -92,6 +93,7 @@ phutil_register_library_map(array(
|
|||
'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php',
|
||||
'AlmanacPropertyDeleteController' => 'applications/almanac/controller/AlmanacPropertyDeleteController.php',
|
||||
'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php',
|
||||
'AlmanacPropertyEditEngine' => 'applications/almanac/editor/AlmanacPropertyEditEngine.php',
|
||||
'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php',
|
||||
'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php',
|
||||
'AlmanacQuery' => 'applications/almanac/query/AlmanacQuery.php',
|
||||
|
@ -106,6 +108,7 @@ phutil_register_library_map(array(
|
|||
'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php',
|
||||
'AlmanacServiceNameNgrams' => 'applications/almanac/storage/AlmanacServiceNameNgrams.php',
|
||||
'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php',
|
||||
'AlmanacServicePropertyEditEngine' => 'applications/almanac/editor/AlmanacServicePropertyEditEngine.php',
|
||||
'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php',
|
||||
'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php',
|
||||
'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php',
|
||||
|
@ -113,6 +116,7 @@ phutil_register_library_map(array(
|
|||
'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php',
|
||||
'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php',
|
||||
'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php',
|
||||
'AlmanacTransaction' => 'applications/almanac/storage/AlmanacTransaction.php',
|
||||
'AphlictDropdownDataQuery' => 'applications/aphlict/query/AphlictDropdownDataQuery.php',
|
||||
'Aphront304Response' => 'aphront/response/Aphront304Response.php',
|
||||
'Aphront400Response' => 'aphront/response/Aphront400Response.php',
|
||||
|
@ -3989,17 +3993,17 @@ phutil_register_library_map(array(
|
|||
'AlmanacBinding' => array(
|
||||
'AlmanacDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorCustomFieldInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'AlmanacPropertyInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
),
|
||||
'AlmanacBindingEditController' => 'AlmanacServiceController',
|
||||
'AlmanacBindingEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'AlmanacBindingEditor' => 'AlmanacEditor',
|
||||
'AlmanacBindingPHIDType' => 'PhabricatorPHIDType',
|
||||
'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine',
|
||||
'AlmanacBindingQuery' => 'AlmanacQuery',
|
||||
'AlmanacBindingTableView' => 'AphrontView',
|
||||
'AlmanacBindingTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'AlmanacBindingTransaction' => 'AlmanacTransaction',
|
||||
'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'AlmanacBindingViewController' => 'AlmanacServiceController',
|
||||
'AlmanacClusterDatabaseServiceType' => 'AlmanacClusterServiceType',
|
||||
|
@ -4008,22 +4012,16 @@ phutil_register_library_map(array(
|
|||
'AlmanacConduitAPIMethod' => 'ConduitAPIMethod',
|
||||
'AlmanacConsoleController' => 'AlmanacController',
|
||||
'AlmanacController' => 'PhabricatorController',
|
||||
'AlmanacCoreCustomField' => array(
|
||||
'AlmanacCustomField',
|
||||
'PhabricatorStandardCustomFieldInterface',
|
||||
),
|
||||
'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateNamespacesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
|
||||
'AlmanacCustomField' => 'PhabricatorCustomField',
|
||||
'AlmanacCustomServiceType' => 'AlmanacServiceType',
|
||||
'AlmanacDAO' => 'PhabricatorLiskDAO',
|
||||
'AlmanacDevice' => array(
|
||||
'AlmanacDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorCustomFieldInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorProjectInterface',
|
||||
'PhabricatorSSHPublicKeyInterface',
|
||||
|
@ -4033,16 +4031,18 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'AlmanacDeviceController' => 'AlmanacController',
|
||||
'AlmanacDeviceEditController' => 'AlmanacDeviceController',
|
||||
'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'AlmanacDeviceEditor' => 'AlmanacEditor',
|
||||
'AlmanacDeviceListController' => 'AlmanacDeviceController',
|
||||
'AlmanacDeviceNameNgrams' => 'PhabricatorSearchNgrams',
|
||||
'AlmanacDevicePHIDType' => 'PhabricatorPHIDType',
|
||||
'AlmanacDevicePropertyEditEngine' => 'AlmanacPropertyEditEngine',
|
||||
'AlmanacDeviceQuery' => 'AlmanacQuery',
|
||||
'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'AlmanacDeviceTransaction' => 'AlmanacTransaction',
|
||||
'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'AlmanacDeviceViewController' => 'AlmanacDeviceController',
|
||||
'AlmanacDrydockPoolServiceType' => 'AlmanacServiceType',
|
||||
'AlmanacEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'AlmanacInterface' => array(
|
||||
'AlmanacDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
|
@ -4065,7 +4065,6 @@ phutil_register_library_map(array(
|
|||
'AlmanacNamespace' => array(
|
||||
'AlmanacDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorCustomFieldInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorProjectInterface',
|
||||
'AlmanacPropertyInterface',
|
||||
|
@ -4104,12 +4103,13 @@ phutil_register_library_map(array(
|
|||
'AlmanacNetworkViewController' => 'AlmanacNetworkController',
|
||||
'AlmanacPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
|
||||
'AlmanacProperty' => array(
|
||||
'PhabricatorCustomFieldStorage',
|
||||
'AlmanacDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'AlmanacPropertyController' => 'AlmanacController',
|
||||
'AlmanacPropertyDeleteController' => 'AlmanacDeviceController',
|
||||
'AlmanacPropertyEditController' => 'AlmanacDeviceController',
|
||||
'AlmanacPropertyEditEngine' => 'PhabricatorEditEngine',
|
||||
'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'AlmanacQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'AlmanacQueryDevicesConduitAPIMethod' => 'AlmanacConduitAPIMethod',
|
||||
|
@ -4118,7 +4118,6 @@ phutil_register_library_map(array(
|
|||
'AlmanacService' => array(
|
||||
'AlmanacDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorCustomFieldInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorProjectInterface',
|
||||
'AlmanacPropertyInterface',
|
||||
|
@ -4128,17 +4127,19 @@ phutil_register_library_map(array(
|
|||
'AlmanacServiceController' => 'AlmanacController',
|
||||
'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'AlmanacServiceEditController' => 'AlmanacServiceController',
|
||||
'AlmanacServiceEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'AlmanacServiceEditor' => 'AlmanacEditor',
|
||||
'AlmanacServiceListController' => 'AlmanacServiceController',
|
||||
'AlmanacServiceNameNgrams' => 'PhabricatorSearchNgrams',
|
||||
'AlmanacServicePHIDType' => 'PhabricatorPHIDType',
|
||||
'AlmanacServicePropertyEditEngine' => 'AlmanacPropertyEditEngine',
|
||||
'AlmanacServiceQuery' => 'AlmanacQuery',
|
||||
'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'AlmanacServiceTransaction' => 'AlmanacTransaction',
|
||||
'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'AlmanacServiceType' => 'Phobject',
|
||||
'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase',
|
||||
'AlmanacServiceViewController' => 'AlmanacServiceController',
|
||||
'AlmanacTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'AphlictDropdownDataQuery' => 'Phobject',
|
||||
'Aphront304Response' => 'AphrontResponse',
|
||||
'Aphront400Response' => 'AphrontResponse',
|
||||
|
|
|
@ -43,12 +43,12 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
|||
return array(
|
||||
'/almanac/' => array(
|
||||
'' => 'AlmanacConsoleController',
|
||||
'service/' => array(
|
||||
'(?P<objectType>service)/' => array(
|
||||
$this->getQueryRoutePattern() => 'AlmanacServiceListController',
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacServiceEditController',
|
||||
'view/(?P<name>[^/]+)/' => 'AlmanacServiceViewController',
|
||||
),
|
||||
'device/' => array(
|
||||
'(?P<objectType>device)/' => array(
|
||||
$this->getQueryRoutePattern() => 'AlmanacDeviceListController',
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacDeviceEditController',
|
||||
'view/(?P<name>[^/]+)/' => 'AlmanacDeviceViewController',
|
||||
|
@ -65,16 +65,16 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
|
|||
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController',
|
||||
'(?P<id>\d+)/' => 'AlmanacNetworkViewController',
|
||||
),
|
||||
'property/' => array(
|
||||
'edit/' => 'AlmanacPropertyEditController',
|
||||
'delete/' => 'AlmanacPropertyDeleteController',
|
||||
),
|
||||
'namespace/' => array(
|
||||
$this->getQueryRoutePattern() => 'AlmanacNamespaceListController',
|
||||
$this->getEditRoutePattern('edit/')
|
||||
=> 'AlmanacNamespaceEditController',
|
||||
'(?P<id>\d+)/' => 'AlmanacNamespaceViewController',
|
||||
),
|
||||
'property/' => array(
|
||||
'delete/' => 'AlmanacPropertyDeleteController',
|
||||
'update/' => 'AlmanacPropertyEditController',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,27 +10,14 @@ abstract class AlmanacController
|
|||
$properties = $object->getAlmanacProperties();
|
||||
|
||||
$this->requireResource('almanac-css');
|
||||
Javelin::initBehavior('phabricator-tooltips', array());
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$object,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
$object,
|
||||
PhabricatorCustomField::ROLE_DEFAULT);
|
||||
|
||||
// Before reading values from the object, read defaults.
|
||||
$defaults = mpull(
|
||||
$field_list->getFields(),
|
||||
'getValueForStorage',
|
||||
'getFieldKey');
|
||||
|
||||
$field_list
|
||||
->setViewer($viewer)
|
||||
->readFieldsFromStorage($object);
|
||||
|
||||
Javelin::initBehavior('phabricator-tooltips', array());
|
||||
$properties = $object->getAlmanacProperties();
|
||||
|
||||
$icon_builtin = id(new PHUIIconView())
|
||||
->setIcon('fa-circle')
|
||||
|
@ -51,45 +38,46 @@ abstract class AlmanacController
|
|||
));
|
||||
|
||||
$builtins = $object->getAlmanacPropertyFieldSpecifications();
|
||||
$defaults = mpull($builtins, null, 'getValueForTransaction');
|
||||
|
||||
// Sort fields so builtin fields appear first, then fields are ordered
|
||||
// alphabetically.
|
||||
$fields = $field_list->getFields();
|
||||
$fields = msort($fields, 'getFieldKey');
|
||||
$properties = msort($properties, 'getFieldName');
|
||||
|
||||
$head = array();
|
||||
$tail = array();
|
||||
foreach ($fields as $field) {
|
||||
$key = $field->getFieldKey();
|
||||
foreach ($properties as $property) {
|
||||
$key = $property->getFieldName();
|
||||
if (isset($builtins[$key])) {
|
||||
$head[$key] = $field;
|
||||
$head[$key] = $property;
|
||||
} else {
|
||||
$tail[$key] = $field;
|
||||
$tail[$key] = $property;
|
||||
}
|
||||
}
|
||||
|
||||
$fields = $head + $tail;
|
||||
$properties = $head + $tail;
|
||||
|
||||
$delete_base = $this->getApplicationURI('property/delete/');
|
||||
$edit_base = $this->getApplicationURI('property/update/');
|
||||
|
||||
$rows = array();
|
||||
foreach ($fields as $key => $field) {
|
||||
$value = $field->getValueForStorage();
|
||||
foreach ($properties as $key => $property) {
|
||||
$value = $property->getFieldValue();
|
||||
|
||||
$is_builtin = isset($builtins[$key]);
|
||||
|
||||
$delete_uri = $this->getApplicationURI('property/delete/');
|
||||
$delete_uri = id(new PhutilURI($delete_uri))
|
||||
$delete_uri = id(new PhutilURI($delete_base))
|
||||
->setQueryParams(
|
||||
array(
|
||||
'objectPHID' => $object->getPHID(),
|
||||
'key' => $key,
|
||||
'objectPHID' => $object->getPHID(),
|
||||
));
|
||||
|
||||
$edit_uri = $this->getApplicationURI('property/edit/');
|
||||
$edit_uri = id(new PhutilURI($edit_uri))
|
||||
$edit_uri = id(new PhutilURI($edit_base))
|
||||
->setQueryParams(
|
||||
array(
|
||||
'objectPHID' => $object->getPHID(),
|
||||
'key' => $key,
|
||||
'objectPHID' => $object->getPHID(),
|
||||
));
|
||||
|
||||
$delete = javelin_tag(
|
||||
|
@ -153,7 +141,8 @@ abstract class AlmanacController
|
|||
));
|
||||
|
||||
$phid = $object->getPHID();
|
||||
$add_uri = $this->getApplicationURI("property/edit/?objectPHID={$phid}");
|
||||
$add_uri = id(new PhutilURI($edit_base))
|
||||
->setQueryParam('objectPHID', $object->getPHID());
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
|
@ -196,4 +185,12 @@ abstract class AlmanacController
|
|||
$box->setInfoView($error_view);
|
||||
}
|
||||
|
||||
protected function getPropertyDeleteURI($object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getPropertyUpdateURI($object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,53 +34,24 @@ final class AlmanacPropertyDeleteController
|
|||
$is_builtin = isset($builtins[$key]);
|
||||
|
||||
if ($is_builtin) {
|
||||
// This is a builtin property, so we're going to reset it to the
|
||||
// default value.
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
$object,
|
||||
PhabricatorCustomField::ROLE_DEFAULT);
|
||||
|
||||
// Note that we're NOT loading field values from the object: we just want
|
||||
// to get the field's default value so we can reset it.
|
||||
|
||||
$fields = $field_list->getFields();
|
||||
$field = $fields[$key];
|
||||
|
||||
$is_delete = false;
|
||||
$new_value = $field->getValueForStorage();
|
||||
|
||||
// Now, load the field to get the old value.
|
||||
|
||||
$field_list
|
||||
->setViewer($viewer)
|
||||
->readFieldsFromStorage($object);
|
||||
|
||||
$old_value = $field->getValueForStorage();
|
||||
|
||||
$title = pht('Reset Property');
|
||||
$body = pht('Reset this property to its default value?');
|
||||
$submit_text = pht('Reset');
|
||||
$body = pht(
|
||||
'Reset property "%s" to its default value?',
|
||||
$key);
|
||||
$submit_text = pht('Reset Property');
|
||||
} else {
|
||||
// This is a custom property, so we're going to delete it outright.
|
||||
$is_delete = true;
|
||||
$old_value = $object->getAlmanacPropertyValue($key);
|
||||
$new_value = null;
|
||||
|
||||
$title = pht('Delete Property');
|
||||
$body = pht('Delete this property? TODO: DOES NOT WORK YET');
|
||||
$submit_text = pht('Delete');
|
||||
$body = pht(
|
||||
'Delete property "%s"?',
|
||||
$key);
|
||||
$submit_text = pht('Delete Property');
|
||||
}
|
||||
|
||||
$validation_exception = null;
|
||||
if ($request->isFormPost()) {
|
||||
$xaction = $object->getApplicationTransactionTemplate()
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD)
|
||||
->setMetadataValue('customfield:key', $key)
|
||||
->setOldValue($old_value)
|
||||
->setNewValue($new_value);
|
||||
|
||||
// TODO: We aren't really deleting properties that we claim to delete
|
||||
// yet, but that needs to be specialized a little bit.
|
||||
->setTransactionType(AlmanacTransaction::TYPE_PROPERTY_REMOVE)
|
||||
->setMetadataValue('almanac.property', $key);
|
||||
|
||||
$editor = $object->getApplicationTransactionEditor()
|
||||
->setActor($viewer)
|
||||
|
|
|
@ -24,134 +24,82 @@ final class AlmanacPropertyEditController
|
|||
}
|
||||
|
||||
$cancel_uri = $object->getURI();
|
||||
$property_key = $request->getStr('key');
|
||||
|
||||
$key = $request->getStr('key');
|
||||
if ($key) {
|
||||
$property_key = $key;
|
||||
|
||||
$is_new = false;
|
||||
$title = pht('Edit Property');
|
||||
$save_button = pht('Save Changes');
|
||||
if (!strlen($property_key)) {
|
||||
return $this->buildPropertyKeyResponse($cancel_uri, null);
|
||||
} else {
|
||||
$property_key = null;
|
||||
|
||||
$is_new = true;
|
||||
$title = pht('Add Property');
|
||||
$save_button = pht('Add Property');
|
||||
}
|
||||
|
||||
if ($is_new) {
|
||||
$errors = array();
|
||||
$property = null;
|
||||
|
||||
$v_name = null;
|
||||
$e_name = true;
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$name = $request->getStr('name');
|
||||
if (!strlen($name)) {
|
||||
$e_name = pht('Required');
|
||||
$errors[] = pht('You must provide a property name.');
|
||||
} else {
|
||||
$caught = null;
|
||||
$error = null;
|
||||
try {
|
||||
AlmanacNames::validateName($name);
|
||||
AlmanacNames::validateName($property_key);
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
$error = $ex->getMessage();
|
||||
}
|
||||
|
||||
// NOTE: If you enter an existing name, we're just treating it as an
|
||||
// edit operation. This might be a little confusing.
|
||||
|
||||
if ($error !== null) {
|
||||
if ($request->isFormPost()) {
|
||||
// The user is creating a new property and picked a bad name. Give
|
||||
// them an opportunity to fix it.
|
||||
return $this->buildPropertyKeyResponse($cancel_uri, $error);
|
||||
} else {
|
||||
// The user is editing an invalid property.
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Invalid Property'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'The property name "%s" is invalid. This property can not '.
|
||||
'be edited.',
|
||||
$property_key))
|
||||
->appendParagraph($error)
|
||||
->addCancelButton($cancel_uri);
|
||||
}
|
||||
if ($caught) {
|
||||
$e_name = pht('Invalid');
|
||||
$errors[] = $caught->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
$property_key = $name;
|
||||
}
|
||||
return $object->newAlmanacPropertyEditEngine()
|
||||
->addContextParameter('objectPHID')
|
||||
->addContextParameter('key')
|
||||
->setTargetObject($object)
|
||||
->setPropertyKey($property_key)
|
||||
->setController($this)
|
||||
->buildResponse();
|
||||
}
|
||||
|
||||
private function buildPropertyKeyResponse($cancel_uri, $error) {
|
||||
$viewer = $this->getViewer();
|
||||
$request = $this->getRequest();
|
||||
$v_key = $request->getStr('key');
|
||||
|
||||
if ($error !== null) {
|
||||
$e_key = pht('Invalid');
|
||||
} else {
|
||||
$e_key = true;
|
||||
}
|
||||
|
||||
if ($property_key === null) {
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('name')
|
||||
->setName('key')
|
||||
->setLabel(pht('Name'))
|
||||
->setValue($v_name)
|
||||
->setError($e_name));
|
||||
->setValue($v_key)
|
||||
->setError($e_key));
|
||||
|
||||
$errors = array();
|
||||
if ($error !== null) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->setErrors($errors)
|
||||
->setTitle(pht('Add Property'))
|
||||
->addHiddenInput('objectPHID', $request->getStr('objectPHID'))
|
||||
->setErrors($errors)
|
||||
->appendForm($form)
|
||||
->addSubmitButton(pht('Continue'))
|
||||
->addCancelButton($cancel_uri);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure property key is appropriate.
|
||||
// TODO: It would be cleaner to put this safety check in the Editor.
|
||||
AlmanacNames::validateName($property_key);
|
||||
|
||||
// If we're adding a new property, put a placeholder on the object so
|
||||
// that we can build a CustomField for it.
|
||||
if (!$object->hasAlmanacProperty($property_key)) {
|
||||
$temporary_property = id(new AlmanacProperty())
|
||||
->setObjectPHID($object->getPHID())
|
||||
->setFieldName($property_key);
|
||||
|
||||
$object->attachAlmanacProperties(array($temporary_property));
|
||||
}
|
||||
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
$object,
|
||||
PhabricatorCustomField::ROLE_DEFAULT);
|
||||
|
||||
// Select only the field being edited.
|
||||
$fields = $field_list->getFields();
|
||||
$fields = array_select_keys($fields, array($property_key));
|
||||
$field_list = new PhabricatorCustomFieldList($fields);
|
||||
|
||||
$field_list
|
||||
->setViewer($viewer)
|
||||
->readFieldsFromStorage($object);
|
||||
|
||||
$validation_exception = null;
|
||||
if ($request->isFormPost() && $request->getStr('isValueEdit')) {
|
||||
$xactions = $field_list->buildFieldTransactionsFromRequest(
|
||||
$object->getApplicationTransactionTemplate(),
|
||||
$request);
|
||||
|
||||
$editor = $object->getApplicationTransactionEditor()
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContinueOnMissingFields(true);
|
||||
|
||||
try {
|
||||
$editor->applyTransactions($object, $xactions);
|
||||
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
|
||||
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||
$validation_exception = $ex;
|
||||
}
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->addHiddenInput('objectPHID', $request->getStr('objectPHID'))
|
||||
->addHiddenInput('key', $request->getStr('key'))
|
||||
->addHiddenInput('name', $property_key)
|
||||
->addHiddenInput('isValueEdit', true);
|
||||
|
||||
$field_list->appendFieldsToForm($form);
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle($title)
|
||||
->setValidationException($validation_exception)
|
||||
->appendForm($form)
|
||||
->addSubmitButton($save_button)
|
||||
->addCancelButton($cancel_uri);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,4 +11,14 @@ abstract class AlmanacServiceController extends AlmanacController {
|
|||
return $crumbs;
|
||||
}
|
||||
|
||||
protected function getPropertyDeleteURI($object) {
|
||||
$id = $object->getID();
|
||||
return "/almanac/service/delete/{$id}/";
|
||||
}
|
||||
|
||||
protected function getPropertyUpdateURI($object) {
|
||||
$id = $object->getID();
|
||||
return "/almanac/service/property/{$id}/";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacCoreCustomField
|
||||
extends AlmanacCustomField
|
||||
implements PhabricatorStandardCustomFieldInterface {
|
||||
|
||||
public function getStandardCustomFieldNamespace() {
|
||||
return 'almanac:core';
|
||||
}
|
||||
|
||||
public function getFieldKey() {
|
||||
return $this->getProxy()->getRawStandardFieldKey();
|
||||
}
|
||||
|
||||
public function getFieldName() {
|
||||
return $this->getFieldKey();
|
||||
}
|
||||
|
||||
public function createFields($object) {
|
||||
if (!$object->getID()) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$specs = $object->getAlmanacPropertyFieldSpecifications();
|
||||
|
||||
$default_specs = array();
|
||||
foreach ($object->getAlmanacProperties() as $property) {
|
||||
$default_specs[$property->getFieldName()] = array(
|
||||
'name' => $property->getFieldName(),
|
||||
'type' => 'text',
|
||||
);
|
||||
}
|
||||
|
||||
return PhabricatorStandardCustomField::buildStandardFields(
|
||||
$this,
|
||||
$specs + $default_specs);
|
||||
}
|
||||
|
||||
public function shouldUseStorage() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
|
||||
$key = $this->getFieldKey();
|
||||
|
||||
if ($object->hasAlmanacProperty($key)) {
|
||||
$this->setValueFromStorage($object->getAlmanacPropertyValue($key));
|
||||
}
|
||||
}
|
||||
|
||||
public function applyApplicationTransactionInternalEffects(
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
public function applyApplicationTransactionExternalEffects(
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
$object = $this->getObject();
|
||||
$phid = $object->getPHID();
|
||||
$key = $this->getFieldKey();
|
||||
|
||||
$property = id(new AlmanacPropertyQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withObjectPHIDs(array($phid))
|
||||
->withNames(array($key))
|
||||
->executeOne();
|
||||
if (!$property) {
|
||||
$property = id(new AlmanacProperty())
|
||||
->setObjectPHID($phid)
|
||||
->setFieldIndex(PhabricatorHash::digestForIndex($key))
|
||||
->setFieldName($key);
|
||||
}
|
||||
|
||||
$property
|
||||
->setFieldValue($xaction->getNewValue())
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<?php
|
||||
|
||||
abstract class AlmanacCustomField
|
||||
extends PhabricatorCustomField {}
|
|
@ -1,11 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorAlmanacApplication';
|
||||
}
|
||||
extends AlmanacEditor {
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Almanac Binding');
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingPropertyEditEngine
|
||||
extends AlmanacPropertyEditEngine {
|
||||
|
||||
const ENGINECONST = 'almanac.binding.property';
|
||||
|
||||
protected function newObjectQuery() {
|
||||
return new AlmanacBindingQuery();
|
||||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
return $object->getURI();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +1,12 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacDeviceEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorAlmanacApplication';
|
||||
}
|
||||
extends AlmanacEditor {
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Almanac Device');
|
||||
}
|
||||
|
||||
protected function supportsSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacDevicePropertyEditEngine
|
||||
extends AlmanacPropertyEditEngine {
|
||||
|
||||
const ENGINECONST = 'almanac.device.property';
|
||||
|
||||
protected function newObjectQuery() {
|
||||
return new AlmanacDeviceQuery();
|
||||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
return $object->getURI();
|
||||
}
|
||||
|
||||
}
|
156
src/applications/almanac/editor/AlmanacEditor.php
Normal file
156
src/applications/almanac/editor/AlmanacEditor.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
abstract class AlmanacEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorAlmanacApplication';
|
||||
}
|
||||
|
||||
protected function supportsSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = AlmanacTransaction::TYPE_PROPERTY_UPDATE;
|
||||
$types[] = AlmanacTransaction::TYPE_PROPERTY_REMOVE;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
|
||||
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
|
||||
$property_key = $xaction->getMetadataValue('almanac.property');
|
||||
$exists = $object->hasAlmanacProperty($property_key);
|
||||
$value = $object->getAlmanacPropertyValue($property_key);
|
||||
return array(
|
||||
'existed' => $exists,
|
||||
'value' => $value,
|
||||
);
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
|
||||
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
|
||||
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
|
||||
$property_key = $xaction->getMetadataValue('almanac.property');
|
||||
if ($object->hasAlmanacProperty($property_key)) {
|
||||
$property = $object->getAlmanacProperty($property_key);
|
||||
} else {
|
||||
$property = id(new AlmanacProperty())
|
||||
->setObjectPHID($object->getPHID())
|
||||
->setFieldName($property_key);
|
||||
}
|
||||
$property
|
||||
->setFieldValue($xaction->getNewValue())
|
||||
->save();
|
||||
return;
|
||||
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
|
||||
$property_key = $xaction->getMetadataValue('almanac.property');
|
||||
if ($object->hasAlmanacProperty($property_key)) {
|
||||
$property = $object->getAlmanacProperty($property_key);
|
||||
$property->delete();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
array $xactions) {
|
||||
|
||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||
|
||||
switch ($type) {
|
||||
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
|
||||
foreach ($xactions as $xaction) {
|
||||
$property_key = $xaction->getMetadataValue('almanac.property');
|
||||
|
||||
$message = null;
|
||||
try {
|
||||
AlmanacNames::validateName($property_key);
|
||||
} catch (Exception $ex) {
|
||||
$message = $ex->getMessage();
|
||||
}
|
||||
|
||||
if ($message !== null) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
$message,
|
||||
$xaction);
|
||||
$errors[] = $error;
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_value = $xaction->getNewValue();
|
||||
try {
|
||||
phutil_json_encode($new_value);
|
||||
} catch (Exception $ex) {
|
||||
$message = pht(
|
||||
'Almanac property values must be representable in JSON. %s',
|
||||
$ex->getMessage());
|
||||
}
|
||||
|
||||
if ($message !== null) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
$message,
|
||||
$xaction);
|
||||
$errors[] = $error;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
|
||||
// NOTE: No name validation on removals since it's OK to delete
|
||||
// an invalid property that somehow came into existence.
|
||||
break;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
abstract class AlmanacPropertyEditEngine
|
||||
extends PhabricatorEditEngine {
|
||||
|
||||
private $propertyKey;
|
||||
|
||||
public function setPropertyKey($property_key) {
|
||||
$this->propertyKey = $property_key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPropertyKey() {
|
||||
return $this->propertyKey;
|
||||
}
|
||||
|
||||
public function isEngineConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isEngineExtensible() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getEngineName() {
|
||||
return pht('Almanac Properties');
|
||||
}
|
||||
|
||||
public function getSummaryHeader() {
|
||||
return pht('Edit Almanac Property Configurations');
|
||||
}
|
||||
|
||||
public function getSummaryText() {
|
||||
return pht('This engine is used to edit Almanac properties.');
|
||||
}
|
||||
|
||||
public function getEngineApplicationClass() {
|
||||
return 'PhabricatorAlmanacApplication';
|
||||
}
|
||||
|
||||
protected function newEditableObject() {
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
protected function getObjectCreateTitleText($object) {
|
||||
return pht('Create Property');
|
||||
}
|
||||
|
||||
protected function getObjectCreateButtonText($object) {
|
||||
return pht('Create Property');
|
||||
}
|
||||
|
||||
protected function getObjectEditTitleText($object) {
|
||||
return pht('Edit Property: %s', $object->getName());
|
||||
}
|
||||
|
||||
protected function getObjectEditShortText($object) {
|
||||
return pht('Edit Property');
|
||||
}
|
||||
|
||||
protected function getObjectCreateShortText() {
|
||||
return pht('Create Property');
|
||||
}
|
||||
|
||||
protected function buildCustomEditFields($object) {
|
||||
$property_key = $this->getPropertyKey();
|
||||
$xaction_type = AlmanacTransaction::TYPE_PROPERTY_UPDATE;
|
||||
|
||||
return array(
|
||||
id(new PhabricatorTextEditField())
|
||||
->setKey('value')
|
||||
->setMetadataValue('almanac.property', $property_key)
|
||||
->setLabel($property_key)
|
||||
->setTransactionType($xaction_type)
|
||||
->setValue($object->getAlmanacPropertyValue($property_key)),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +1,12 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacServiceEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorAlmanacApplication';
|
||||
}
|
||||
extends AlmanacEditor {
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Almanac Service');
|
||||
}
|
||||
|
||||
protected function supportsSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
|
@ -185,6 +177,4 @@ final class AlmanacServiceEditor
|
|||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacServicePropertyEditEngine
|
||||
extends AlmanacPropertyEditEngine {
|
||||
|
||||
const ENGINECONST = 'almanac.service.property';
|
||||
|
||||
protected function newObjectQuery() {
|
||||
return new AlmanacServiceQuery();
|
||||
}
|
||||
|
||||
protected function getObjectViewURI($object) {
|
||||
return $object->getURI();
|
||||
}
|
||||
|
||||
}
|
|
@ -8,5 +8,6 @@ interface AlmanacPropertyInterface {
|
|||
public function getAlmanacProperty($key);
|
||||
public function getAlmanacPropertyValue($key, $default = null);
|
||||
public function getAlmanacPropertyFieldSpecifications();
|
||||
public function newAlmanacPropertyEditEngine();
|
||||
|
||||
}
|
||||
|
|
|
@ -5,9 +5,8 @@ abstract class AlmanacQuery
|
|||
|
||||
protected function didFilterPage(array $objects) {
|
||||
if (head($objects) instanceof AlmanacPropertyInterface) {
|
||||
// NOTE: We load properties unconditionally because CustomField assumes
|
||||
// it can always generate a list of fields on an object. It may make
|
||||
// sense to re-examine that assumption eventually.
|
||||
// NOTE: We load properties for obsolete historical reasons. It may make
|
||||
// sense to re-examine that assumption shortly.
|
||||
|
||||
$property_query = id(new AlmanacPropertyQuery())
|
||||
->setViewer($this->getViewer())
|
||||
|
@ -25,9 +24,23 @@ abstract class AlmanacQuery
|
|||
$properties = mgroup($properties, 'getObjectPHID');
|
||||
foreach ($objects as $object) {
|
||||
$object_properties = idx($properties, $object->getPHID(), array());
|
||||
$object_properties = mpull($object_properties, null, 'getFieldName');
|
||||
|
||||
// Create synthetic properties for defaults on the object itself.
|
||||
$specs = $object->getAlmanacPropertyFieldSpecifications();
|
||||
foreach ($specs as $key => $spec) {
|
||||
if (empty($object_properties[$key])) {
|
||||
$object_properties[$key] = id(new AlmanacProperty())
|
||||
->setObjectPHID($object->getPHID())
|
||||
->setFieldName($key)
|
||||
->setFieldValue($spec->getValueForTransaction());
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($object_properties as $property) {
|
||||
$property->attachObject($object);
|
||||
}
|
||||
|
||||
$object->attachAlmanacProperties($object_properties);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,4 @@ final class AlmanacClusterDatabaseServiceType
|
|||
'Defines a database service for use in a Phabricator cluster.');
|
||||
}
|
||||
|
||||
public function getFieldSpecifications() {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,16 +18,7 @@ final class AlmanacClusterRepositoryServiceType
|
|||
|
||||
public function getFieldSpecifications() {
|
||||
return array(
|
||||
'closed' => array(
|
||||
'type' => 'bool',
|
||||
'name' => pht('Closed'),
|
||||
'default' => false,
|
||||
'strings' => array(
|
||||
'edit.checkbox' => pht(
|
||||
'Prevent new repositories from being allocated on this '.
|
||||
'service.'),
|
||||
),
|
||||
),
|
||||
'closed' => id(new PhabricatorTextEditField()),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ final class AlmanacBinding
|
|||
extends AlmanacDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorCustomFieldInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
AlmanacPropertyInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
|
@ -17,7 +16,6 @@ final class AlmanacBinding
|
|||
private $service = self::ATTACHABLE;
|
||||
private $device = self::ATTACHABLE;
|
||||
private $interface = self::ATTACHABLE;
|
||||
private $customFields = self::ATTACHABLE;
|
||||
private $almanacProperties = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewBinding(AlmanacService $service) {
|
||||
|
@ -58,6 +56,10 @@ final class AlmanacBinding
|
|||
return parent::save();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return pht('Binding %s', $this->getID());
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return '/almanac/binding/'.$this->getID().'/';
|
||||
}
|
||||
|
@ -124,6 +126,10 @@ final class AlmanacBinding
|
|||
return array();
|
||||
}
|
||||
|
||||
public function newAlmanacPropertyEditEngine() {
|
||||
return new AlmanacBindingPropertyEditEngine();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
@ -162,27 +168,6 @@ final class AlmanacBinding
|
|||
}
|
||||
|
||||
|
||||
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
||||
|
||||
|
||||
public function getCustomFieldSpecificationForRole($role) {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getCustomFieldBaseClass() {
|
||||
return 'AlmanacCustomField';
|
||||
}
|
||||
|
||||
public function getCustomFields() {
|
||||
return $this->assertAttached($this->customFields);
|
||||
}
|
||||
|
||||
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
|
||||
$this->customFields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacBindingTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
extends AlmanacTransaction {
|
||||
|
||||
const TYPE_INTERFACE = 'almanac:binding:interface';
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ final class AlmanacDevice
|
|||
extends AlmanacDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorCustomFieldInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorProjectInterface,
|
||||
PhabricatorSSHPublicKeyInterface,
|
||||
|
@ -19,7 +18,6 @@ final class AlmanacDevice
|
|||
protected $editPolicy;
|
||||
protected $isLocked;
|
||||
|
||||
private $customFields = self::ATTACHABLE;
|
||||
private $almanacProperties = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewDevice() {
|
||||
|
@ -137,6 +135,10 @@ final class AlmanacDevice
|
|||
return array();
|
||||
}
|
||||
|
||||
public function newAlmanacPropertyEditEngine() {
|
||||
return new AlmanacDevicePropertyEditEngine();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
@ -178,27 +180,6 @@ final class AlmanacDevice
|
|||
}
|
||||
|
||||
|
||||
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
||||
|
||||
|
||||
public function getCustomFieldSpecificationForRole($role) {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getCustomFieldBaseClass() {
|
||||
return 'AlmanacCustomField';
|
||||
}
|
||||
|
||||
public function getCustomFields() {
|
||||
return $this->assertAttached($this->customFields);
|
||||
}
|
||||
|
||||
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
|
||||
$this->customFields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacDeviceTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
extends AlmanacTransaction {
|
||||
|
||||
const TYPE_NAME = 'almanac:device:name';
|
||||
const TYPE_INTERFACE = 'almanac:device:interface';
|
||||
|
|
|
@ -4,7 +4,6 @@ final class AlmanacNamespace
|
|||
extends AlmanacDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorCustomFieldInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorProjectInterface,
|
||||
AlmanacPropertyInterface,
|
||||
|
@ -17,7 +16,6 @@ final class AlmanacNamespace
|
|||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
|
||||
private $customFields = self::ATTACHABLE;
|
||||
private $almanacProperties = self::ATTACHABLE;
|
||||
|
||||
public static function initializeNewNamespace() {
|
||||
|
@ -148,6 +146,10 @@ final class AlmanacNamespace
|
|||
return array();
|
||||
}
|
||||
|
||||
public function newAlmanacPropertyEditEngine() {
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
@ -177,27 +179,6 @@ final class AlmanacNamespace
|
|||
}
|
||||
|
||||
|
||||
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
||||
|
||||
|
||||
public function getCustomFieldSpecificationForRole($role) {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getCustomFieldBaseClass() {
|
||||
return 'AlmanacCustomField';
|
||||
}
|
||||
|
||||
public function getCustomFields() {
|
||||
return $this->assertAttached($this->customFields);
|
||||
}
|
||||
|
||||
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
|
||||
$this->customFields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacProperty
|
||||
extends PhabricatorCustomFieldStorage
|
||||
extends AlmanacDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $objectPHID;
|
||||
protected $fieldIndex;
|
||||
protected $fieldName;
|
||||
protected $fieldValue;
|
||||
|
||||
private $object = self::ATTACHABLE;
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'almanac';
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
$config = parent::getConfiguration();
|
||||
|
||||
$config[self::CONFIG_COLUMN_SCHEMA] += array(
|
||||
return array(
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'fieldValue' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'fieldIndex' => 'bytes12',
|
||||
'fieldName' => 'text128',
|
||||
);
|
||||
|
||||
return $config;
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'objectPHID' => array(
|
||||
'columns' => array('objectPHID', 'fieldIndex'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function getObject() {
|
||||
|
@ -31,37 +39,11 @@ final class AlmanacProperty
|
|||
return $this;
|
||||
}
|
||||
|
||||
public static function buildTransactions(
|
||||
AlmanacPropertyInterface $object,
|
||||
array $properties) {
|
||||
public function save() {
|
||||
$hash = PhabricatorHash::digestForIndex($this->getFieldName());
|
||||
$this->setFieldIndex($hash);
|
||||
|
||||
$template = $object->getApplicationTransactionTemplate();
|
||||
|
||||
$attached_properties = $object->getAlmanacProperties();
|
||||
foreach ($properties as $key => $value) {
|
||||
if (empty($attached_properties[$key])) {
|
||||
$attached_properties[] = id(new AlmanacProperty())
|
||||
->setObjectPHID($object->getPHID())
|
||||
->setFieldName($key);
|
||||
}
|
||||
}
|
||||
$object->attachAlmanacProperties($attached_properties);
|
||||
|
||||
$field_list = PhabricatorCustomField::getObjectFields(
|
||||
$object,
|
||||
PhabricatorCustomField::ROLE_DEFAULT);
|
||||
$fields = $field_list->getFields();
|
||||
|
||||
$xactions = array();
|
||||
foreach ($properties as $name => $property) {
|
||||
$xactions[] = id(clone $template)
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD)
|
||||
->setMetadataValue('customfield:key', $name)
|
||||
->setOldValue($object->getAlmanacPropertyValue($name))
|
||||
->setNewValue($property);
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ final class AlmanacService
|
|||
extends AlmanacDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorCustomFieldInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorProjectInterface,
|
||||
AlmanacPropertyInterface,
|
||||
|
@ -19,7 +18,6 @@ final class AlmanacService
|
|||
protected $serviceClass;
|
||||
protected $isLocked;
|
||||
|
||||
private $customFields = self::ATTACHABLE;
|
||||
private $almanacProperties = self::ATTACHABLE;
|
||||
private $bindings = self::ATTACHABLE;
|
||||
private $serviceType = self::ATTACHABLE;
|
||||
|
@ -130,6 +128,10 @@ final class AlmanacService
|
|||
return $this->getServiceType()->getFieldSpecifications();
|
||||
}
|
||||
|
||||
public function newAlmanacPropertyEditEngine() {
|
||||
return new AlmanacServicePropertyEditEngine();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
@ -171,27 +173,6 @@ final class AlmanacService
|
|||
}
|
||||
|
||||
|
||||
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
|
||||
|
||||
|
||||
public function getCustomFieldSpecificationForRole($role) {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getCustomFieldBaseClass() {
|
||||
return 'AlmanacCustomField';
|
||||
}
|
||||
|
||||
public function getCustomFields() {
|
||||
return $this->assertAttached($this->customFields);
|
||||
}
|
||||
|
||||
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
|
||||
$this->customFields = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -1,23 +1,15 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacServiceTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
extends AlmanacTransaction {
|
||||
|
||||
const TYPE_NAME = 'almanac:service:name';
|
||||
const TYPE_LOCK = 'almanac:service:lock';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'almanac';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return AlmanacServicePHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionCommentObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
|
||||
|
|
38
src/applications/almanac/storage/AlmanacTransaction.php
Normal file
38
src/applications/almanac/storage/AlmanacTransaction.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
abstract class AlmanacTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const TYPE_PROPERTY_UPDATE = 'almanac:property:update';
|
||||
const TYPE_PROPERTY_REMOVE = 'almanac:property:remove';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'almanac';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionCommentObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
$author_phid = $this->getAuthorPHID();
|
||||
|
||||
switch ($this->getTransactionType()) {
|
||||
case self::TYPE_PROPERTY_UPDATE:
|
||||
$property_key = $this->getMetadataValue('almanac.property');
|
||||
return pht(
|
||||
'%s updated the property "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$property_key);
|
||||
case self::TYPE_PROPERTY_REMOVE:
|
||||
$property_key = $this->getMetadataValue('almanac.property');
|
||||
return pht(
|
||||
'%s deleted the property "%s".',
|
||||
$this->renderHandleLink($author_phid),
|
||||
$property_key);
|
||||
}
|
||||
|
||||
return parent::getTitle();
|
||||
}
|
||||
|
||||
}
|
|
@ -23,6 +23,7 @@ abstract class PhabricatorEditEngine
|
|||
private $isCreate;
|
||||
private $editEngineConfiguration;
|
||||
private $contextParameters = array();
|
||||
private $targetObject;
|
||||
|
||||
final public function setViewer(PhabricatorUser $viewer) {
|
||||
$this->viewer = $viewer;
|
||||
|
@ -61,6 +62,22 @@ abstract class PhabricatorEditEngine
|
|||
return true;
|
||||
}
|
||||
|
||||
public function isEngineExtensible() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the engine to edit a particular object.
|
||||
*/
|
||||
public function setTargetObject($target_object) {
|
||||
$this->targetObject = $target_object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTargetObject() {
|
||||
return $this->targetObject;
|
||||
}
|
||||
|
||||
|
||||
/* -( Managing Fields )---------------------------------------------------- */
|
||||
|
||||
|
@ -94,7 +111,12 @@ abstract class PhabricatorEditEngine
|
|||
|
||||
$fields = mpull($fields, null, 'getKey');
|
||||
|
||||
if ($this->isEngineExtensible()) {
|
||||
$extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions();
|
||||
} else {
|
||||
$extensions = array();
|
||||
}
|
||||
|
||||
foreach ($extensions as $extension) {
|
||||
$extension->setViewer($viewer);
|
||||
|
||||
|
@ -720,6 +742,8 @@ abstract class PhabricatorEditEngine
|
|||
break;
|
||||
}
|
||||
|
||||
$object = $this->getTargetObject();
|
||||
if (!$object) {
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
if ($id) {
|
||||
|
@ -738,6 +762,9 @@ abstract class PhabricatorEditEngine
|
|||
$this->setIsCreate(true);
|
||||
$object = $this->newEditableObject();
|
||||
}
|
||||
} else {
|
||||
$id = $object->getID();
|
||||
}
|
||||
|
||||
$this->validateObject($object);
|
||||
|
||||
|
@ -831,7 +858,7 @@ abstract class PhabricatorEditEngine
|
|||
$template = $object->getApplicationTransactionTemplate();
|
||||
|
||||
$validation_exception = null;
|
||||
if ($request->isFormPost()) {
|
||||
if ($request->isFormPost() && $request->getBool('editEngine')) {
|
||||
$submit_fields = $fields;
|
||||
|
||||
foreach ($submit_fields as $key => $field) {
|
||||
|
@ -1044,7 +1071,8 @@ abstract class PhabricatorEditEngine
|
|||
$request = $controller->getRequest();
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer);
|
||||
->setUser($viewer)
|
||||
->addHiddenInput('editEngine', 'true');
|
||||
|
||||
foreach ($this->contextParameters as $param) {
|
||||
$form->addHiddenInput($param, $request->getStr($param));
|
||||
|
|
|
@ -456,8 +456,14 @@ abstract class PhabricatorApplicationTransaction
|
|||
return null;
|
||||
}
|
||||
|
||||
$object = $this->getObject();
|
||||
|
||||
if (!($object instanceof PhabricatorCustomFieldInterface)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$field = PhabricatorCustomField::getObjectField(
|
||||
$this->getObject(),
|
||||
$object,
|
||||
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
|
||||
$key);
|
||||
if (!$field) {
|
||||
|
|
Loading…
Reference in a new issue