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

Configure Feed

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

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

+598 -491
+28
resources/sql/autopatches/20160222.almanac.1.properties.php
··· 1 + <?php 2 + 3 + $table = new AlmanacProperty(); 4 + $conn_w = $table->establishConnection('w'); 5 + 6 + // We're going to JSON-encode the value in each row: previously rows stored 7 + // plain strings, but now they store JSON, so we need to update them. 8 + 9 + foreach (new LiskMigrationIterator($table) as $property) { 10 + $key = $property->getFieldName(); 11 + 12 + $current_row = queryfx_one( 13 + $conn_w, 14 + 'SELECT fieldValue FROM %T WHERE id = %d', 15 + $table->getTableName(), 16 + $property->getID()); 17 + 18 + if (!$current_row) { 19 + continue; 20 + } 21 + 22 + queryfx( 23 + $conn_w, 24 + 'UPDATE %T SET fieldValue = %s WHERE id = %d', 25 + $table->getTableName(), 26 + phutil_json_encode($current_row['fieldValue']), 27 + $property->getID()); 28 + }
+19 -18
src/__phutil_library_map__.php
··· 14 14 'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php', 15 15 'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php', 16 16 'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php', 17 + 'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php', 17 18 'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php', 18 19 'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php', 19 20 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', ··· 25 26 'AlmanacConduitAPIMethod' => 'applications/almanac/conduit/AlmanacConduitAPIMethod.php', 26 27 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 27 28 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', 28 - 'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php', 29 29 'AlmanacCreateClusterServicesCapability' => 'applications/almanac/capability/AlmanacCreateClusterServicesCapability.php', 30 30 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', 31 31 'AlmanacCreateNamespacesCapability' => 'applications/almanac/capability/AlmanacCreateNamespacesCapability.php', 32 32 'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php', 33 33 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 34 - 'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php', 35 34 'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php', 36 35 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 37 36 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', ··· 41 40 'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php', 42 41 'AlmanacDeviceNameNgrams' => 'applications/almanac/storage/AlmanacDeviceNameNgrams.php', 43 42 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', 43 + 'AlmanacDevicePropertyEditEngine' => 'applications/almanac/editor/AlmanacDevicePropertyEditEngine.php', 44 44 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', 45 45 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', 46 46 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 47 47 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 48 48 'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php', 49 49 'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php', 50 + 'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php', 50 51 'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php', 51 52 'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php', 52 53 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', ··· 92 93 'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php', 93 94 'AlmanacPropertyDeleteController' => 'applications/almanac/controller/AlmanacPropertyDeleteController.php', 94 95 'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php', 96 + 'AlmanacPropertyEditEngine' => 'applications/almanac/editor/AlmanacPropertyEditEngine.php', 95 97 'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php', 96 98 'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php', 97 99 'AlmanacQuery' => 'applications/almanac/query/AlmanacQuery.php', ··· 106 108 'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php', 107 109 'AlmanacServiceNameNgrams' => 'applications/almanac/storage/AlmanacServiceNameNgrams.php', 108 110 'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php', 111 + 'AlmanacServicePropertyEditEngine' => 'applications/almanac/editor/AlmanacServicePropertyEditEngine.php', 109 112 'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php', 110 113 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', 111 114 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', ··· 113 116 'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php', 114 117 'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php', 115 118 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', 119 + 'AlmanacTransaction' => 'applications/almanac/storage/AlmanacTransaction.php', 116 120 'AphlictDropdownDataQuery' => 'applications/aphlict/query/AphlictDropdownDataQuery.php', 117 121 'Aphront304Response' => 'aphront/response/Aphront304Response.php', 118 122 'Aphront400Response' => 'aphront/response/Aphront400Response.php', ··· 3989 3993 'AlmanacBinding' => array( 3990 3994 'AlmanacDAO', 3991 3995 'PhabricatorPolicyInterface', 3992 - 'PhabricatorCustomFieldInterface', 3993 3996 'PhabricatorApplicationTransactionInterface', 3994 3997 'AlmanacPropertyInterface', 3995 3998 'PhabricatorDestructibleInterface', 3996 3999 ), 3997 4000 'AlmanacBindingEditController' => 'AlmanacServiceController', 3998 - 'AlmanacBindingEditor' => 'PhabricatorApplicationTransactionEditor', 4001 + 'AlmanacBindingEditor' => 'AlmanacEditor', 3999 4002 'AlmanacBindingPHIDType' => 'PhabricatorPHIDType', 4003 + 'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine', 4000 4004 'AlmanacBindingQuery' => 'AlmanacQuery', 4001 4005 'AlmanacBindingTableView' => 'AphrontView', 4002 - 'AlmanacBindingTransaction' => 'PhabricatorApplicationTransaction', 4006 + 'AlmanacBindingTransaction' => 'AlmanacTransaction', 4003 4007 'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4004 4008 'AlmanacBindingViewController' => 'AlmanacServiceController', 4005 4009 'AlmanacClusterDatabaseServiceType' => 'AlmanacClusterServiceType', ··· 4008 4012 'AlmanacConduitAPIMethod' => 'ConduitAPIMethod', 4009 4013 'AlmanacConsoleController' => 'AlmanacController', 4010 4014 'AlmanacController' => 'PhabricatorController', 4011 - 'AlmanacCoreCustomField' => array( 4012 - 'AlmanacCustomField', 4013 - 'PhabricatorStandardCustomFieldInterface', 4014 - ), 4015 4015 'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability', 4016 4016 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', 4017 4017 'AlmanacCreateNamespacesCapability' => 'PhabricatorPolicyCapability', 4018 4018 'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability', 4019 4019 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 4020 - 'AlmanacCustomField' => 'PhabricatorCustomField', 4021 4020 'AlmanacCustomServiceType' => 'AlmanacServiceType', 4022 4021 'AlmanacDAO' => 'PhabricatorLiskDAO', 4023 4022 'AlmanacDevice' => array( 4024 4023 'AlmanacDAO', 4025 4024 'PhabricatorPolicyInterface', 4026 - 'PhabricatorCustomFieldInterface', 4027 4025 'PhabricatorApplicationTransactionInterface', 4028 4026 'PhabricatorProjectInterface', 4029 4027 'PhabricatorSSHPublicKeyInterface', ··· 4033 4031 ), 4034 4032 'AlmanacDeviceController' => 'AlmanacController', 4035 4033 'AlmanacDeviceEditController' => 'AlmanacDeviceController', 4036 - 'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor', 4034 + 'AlmanacDeviceEditor' => 'AlmanacEditor', 4037 4035 'AlmanacDeviceListController' => 'AlmanacDeviceController', 4038 4036 'AlmanacDeviceNameNgrams' => 'PhabricatorSearchNgrams', 4039 4037 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 4038 + 'AlmanacDevicePropertyEditEngine' => 'AlmanacPropertyEditEngine', 4040 4039 'AlmanacDeviceQuery' => 'AlmanacQuery', 4041 4040 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', 4042 - 'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction', 4041 + 'AlmanacDeviceTransaction' => 'AlmanacTransaction', 4043 4042 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4044 4043 'AlmanacDeviceViewController' => 'AlmanacDeviceController', 4045 4044 'AlmanacDrydockPoolServiceType' => 'AlmanacServiceType', 4045 + 'AlmanacEditor' => 'PhabricatorApplicationTransactionEditor', 4046 4046 'AlmanacInterface' => array( 4047 4047 'AlmanacDAO', 4048 4048 'PhabricatorPolicyInterface', ··· 4065 4065 'AlmanacNamespace' => array( 4066 4066 'AlmanacDAO', 4067 4067 'PhabricatorPolicyInterface', 4068 - 'PhabricatorCustomFieldInterface', 4069 4068 'PhabricatorApplicationTransactionInterface', 4070 4069 'PhabricatorProjectInterface', 4071 4070 'AlmanacPropertyInterface', ··· 4104 4103 'AlmanacNetworkViewController' => 'AlmanacNetworkController', 4105 4104 'AlmanacPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 4106 4105 'AlmanacProperty' => array( 4107 - 'PhabricatorCustomFieldStorage', 4106 + 'AlmanacDAO', 4108 4107 'PhabricatorPolicyInterface', 4109 4108 ), 4110 4109 'AlmanacPropertyController' => 'AlmanacController', 4111 4110 'AlmanacPropertyDeleteController' => 'AlmanacDeviceController', 4112 4111 'AlmanacPropertyEditController' => 'AlmanacDeviceController', 4112 + 'AlmanacPropertyEditEngine' => 'PhabricatorEditEngine', 4113 4113 'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4114 4114 'AlmanacQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4115 4115 'AlmanacQueryDevicesConduitAPIMethod' => 'AlmanacConduitAPIMethod', ··· 4118 4118 'AlmanacService' => array( 4119 4119 'AlmanacDAO', 4120 4120 'PhabricatorPolicyInterface', 4121 - 'PhabricatorCustomFieldInterface', 4122 4121 'PhabricatorApplicationTransactionInterface', 4123 4122 'PhabricatorProjectInterface', 4124 4123 'AlmanacPropertyInterface', ··· 4128 4127 'AlmanacServiceController' => 'AlmanacController', 4129 4128 'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource', 4130 4129 'AlmanacServiceEditController' => 'AlmanacServiceController', 4131 - 'AlmanacServiceEditor' => 'PhabricatorApplicationTransactionEditor', 4130 + 'AlmanacServiceEditor' => 'AlmanacEditor', 4132 4131 'AlmanacServiceListController' => 'AlmanacServiceController', 4133 4132 'AlmanacServiceNameNgrams' => 'PhabricatorSearchNgrams', 4134 4133 'AlmanacServicePHIDType' => 'PhabricatorPHIDType', 4134 + 'AlmanacServicePropertyEditEngine' => 'AlmanacPropertyEditEngine', 4135 4135 'AlmanacServiceQuery' => 'AlmanacQuery', 4136 4136 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', 4137 - 'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction', 4137 + 'AlmanacServiceTransaction' => 'AlmanacTransaction', 4138 4138 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4139 4139 'AlmanacServiceType' => 'Phobject', 4140 4140 'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase', 4141 4141 'AlmanacServiceViewController' => 'AlmanacServiceController', 4142 + 'AlmanacTransaction' => 'PhabricatorApplicationTransaction', 4142 4143 'AphlictDropdownDataQuery' => 'Phobject', 4143 4144 'Aphront304Response' => 'AphrontResponse', 4144 4145 'Aphront400Response' => 'AphrontResponse',
+6 -6
src/applications/almanac/application/PhabricatorAlmanacApplication.php
··· 43 43 return array( 44 44 '/almanac/' => array( 45 45 '' => 'AlmanacConsoleController', 46 - 'service/' => array( 46 + '(?P<objectType>service)/' => array( 47 47 $this->getQueryRoutePattern() => 'AlmanacServiceListController', 48 48 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacServiceEditController', 49 49 'view/(?P<name>[^/]+)/' => 'AlmanacServiceViewController', 50 50 ), 51 - 'device/' => array( 51 + '(?P<objectType>device)/' => array( 52 52 $this->getQueryRoutePattern() => 'AlmanacDeviceListController', 53 53 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacDeviceEditController', 54 54 'view/(?P<name>[^/]+)/' => 'AlmanacDeviceViewController', ··· 65 65 'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController', 66 66 '(?P<id>\d+)/' => 'AlmanacNetworkViewController', 67 67 ), 68 - 'property/' => array( 69 - 'edit/' => 'AlmanacPropertyEditController', 70 - 'delete/' => 'AlmanacPropertyDeleteController', 71 - ), 72 68 'namespace/' => array( 73 69 $this->getQueryRoutePattern() => 'AlmanacNamespaceListController', 74 70 $this->getEditRoutePattern('edit/') 75 71 => 'AlmanacNamespaceEditController', 76 72 '(?P<id>\d+)/' => 'AlmanacNamespaceViewController', 73 + ), 74 + 'property/' => array( 75 + 'delete/' => 'AlmanacPropertyDeleteController', 76 + 'update/' => 'AlmanacPropertyEditController', 77 77 ), 78 78 ), 79 79 );
+28 -31
src/applications/almanac/controller/AlmanacController.php
··· 10 10 $properties = $object->getAlmanacProperties(); 11 11 12 12 $this->requireResource('almanac-css'); 13 + Javelin::initBehavior('phabricator-tooltips', array()); 13 14 14 15 $can_edit = PhabricatorPolicyFilter::hasCapability( 15 16 $viewer, 16 17 $object, 17 18 PhabricatorPolicyCapability::CAN_EDIT); 18 19 19 - $field_list = PhabricatorCustomField::getObjectFields( 20 - $object, 21 - PhabricatorCustomField::ROLE_DEFAULT); 22 - 23 - // Before reading values from the object, read defaults. 24 - $defaults = mpull( 25 - $field_list->getFields(), 26 - 'getValueForStorage', 27 - 'getFieldKey'); 28 - 29 - $field_list 30 - ->setViewer($viewer) 31 - ->readFieldsFromStorage($object); 32 - 33 - Javelin::initBehavior('phabricator-tooltips', array()); 20 + $properties = $object->getAlmanacProperties(); 34 21 35 22 $icon_builtin = id(new PHUIIconView()) 36 23 ->setIcon('fa-circle') ··· 51 38 )); 52 39 53 40 $builtins = $object->getAlmanacPropertyFieldSpecifications(); 41 + $defaults = mpull($builtins, null, 'getValueForTransaction'); 54 42 55 43 // Sort fields so builtin fields appear first, then fields are ordered 56 44 // alphabetically. 57 - $fields = $field_list->getFields(); 58 - $fields = msort($fields, 'getFieldKey'); 45 + $properties = msort($properties, 'getFieldName'); 59 46 60 47 $head = array(); 61 48 $tail = array(); 62 - foreach ($fields as $field) { 63 - $key = $field->getFieldKey(); 49 + foreach ($properties as $property) { 50 + $key = $property->getFieldName(); 64 51 if (isset($builtins[$key])) { 65 - $head[$key] = $field; 52 + $head[$key] = $property; 66 53 } else { 67 - $tail[$key] = $field; 54 + $tail[$key] = $property; 68 55 } 69 56 } 70 57 71 - $fields = $head + $tail; 58 + $properties = $head + $tail; 59 + 60 + $delete_base = $this->getApplicationURI('property/delete/'); 61 + $edit_base = $this->getApplicationURI('property/update/'); 72 62 73 63 $rows = array(); 74 - foreach ($fields as $key => $field) { 75 - $value = $field->getValueForStorage(); 64 + foreach ($properties as $key => $property) { 65 + $value = $property->getFieldValue(); 76 66 77 67 $is_builtin = isset($builtins[$key]); 78 68 79 - $delete_uri = $this->getApplicationURI('property/delete/'); 80 - $delete_uri = id(new PhutilURI($delete_uri)) 69 + $delete_uri = id(new PhutilURI($delete_base)) 81 70 ->setQueryParams( 82 71 array( 72 + 'key' => $key, 83 73 'objectPHID' => $object->getPHID(), 84 - 'key' => $key, 85 74 )); 86 75 87 - $edit_uri = $this->getApplicationURI('property/edit/'); 88 - $edit_uri = id(new PhutilURI($edit_uri)) 76 + $edit_uri = id(new PhutilURI($edit_base)) 89 77 ->setQueryParams( 90 78 array( 79 + 'key' => $key, 91 80 'objectPHID' => $object->getPHID(), 92 - 'key' => $key, 93 81 )); 94 82 95 83 $delete = javelin_tag( ··· 153 141 )); 154 142 155 143 $phid = $object->getPHID(); 156 - $add_uri = $this->getApplicationURI("property/edit/?objectPHID={$phid}"); 144 + $add_uri = id(new PhutilURI($edit_base)) 145 + ->setQueryParam('objectPHID', $object->getPHID()); 157 146 158 147 $can_edit = PhabricatorPolicyFilter::hasCapability( 159 148 $viewer, ··· 194 183 )); 195 184 196 185 $box->setInfoView($error_view); 186 + } 187 + 188 + protected function getPropertyDeleteURI($object) { 189 + return null; 190 + } 191 + 192 + protected function getPropertyUpdateURI($object) { 193 + return null; 197 194 } 198 195 199 196 }
+10 -39
src/applications/almanac/controller/AlmanacPropertyDeleteController.php
··· 34 34 $is_builtin = isset($builtins[$key]); 35 35 36 36 if ($is_builtin) { 37 - // This is a builtin property, so we're going to reset it to the 38 - // default value. 39 - $field_list = PhabricatorCustomField::getObjectFields( 40 - $object, 41 - PhabricatorCustomField::ROLE_DEFAULT); 42 - 43 - // Note that we're NOT loading field values from the object: we just want 44 - // to get the field's default value so we can reset it. 45 - 46 - $fields = $field_list->getFields(); 47 - $field = $fields[$key]; 48 - 49 - $is_delete = false; 50 - $new_value = $field->getValueForStorage(); 51 - 52 - // Now, load the field to get the old value. 53 - 54 - $field_list 55 - ->setViewer($viewer) 56 - ->readFieldsFromStorage($object); 57 - 58 - $old_value = $field->getValueForStorage(); 59 - 60 37 $title = pht('Reset Property'); 61 - $body = pht('Reset this property to its default value?'); 62 - $submit_text = pht('Reset'); 38 + $body = pht( 39 + 'Reset property "%s" to its default value?', 40 + $key); 41 + $submit_text = pht('Reset Property'); 63 42 } else { 64 - // This is a custom property, so we're going to delete it outright. 65 - $is_delete = true; 66 - $old_value = $object->getAlmanacPropertyValue($key); 67 - $new_value = null; 68 - 69 43 $title = pht('Delete Property'); 70 - $body = pht('Delete this property? TODO: DOES NOT WORK YET'); 71 - $submit_text = pht('Delete'); 44 + $body = pht( 45 + 'Delete property "%s"?', 46 + $key); 47 + $submit_text = pht('Delete Property'); 72 48 } 73 49 74 50 $validation_exception = null; 75 51 if ($request->isFormPost()) { 76 52 $xaction = $object->getApplicationTransactionTemplate() 77 - ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) 78 - ->setMetadataValue('customfield:key', $key) 79 - ->setOldValue($old_value) 80 - ->setNewValue($new_value); 81 - 82 - // TODO: We aren't really deleting properties that we claim to delete 83 - // yet, but that needs to be specialized a little bit. 53 + ->setTransactionType(AlmanacTransaction::TYPE_PROPERTY_REMOVE) 54 + ->setMetadataValue('almanac.property', $key); 84 55 85 56 $editor = $object->getApplicationTransactionEditor() 86 57 ->setActor($viewer)
+56 -108
src/applications/almanac/controller/AlmanacPropertyEditController.php
··· 24 24 } 25 25 26 26 $cancel_uri = $object->getURI(); 27 + $property_key = $request->getStr('key'); 27 28 28 - $key = $request->getStr('key'); 29 - if ($key) { 30 - $property_key = $key; 31 - 32 - $is_new = false; 33 - $title = pht('Edit Property'); 34 - $save_button = pht('Save Changes'); 29 + if (!strlen($property_key)) { 30 + return $this->buildPropertyKeyResponse($cancel_uri, null); 35 31 } else { 36 - $property_key = null; 32 + $error = null; 33 + try { 34 + AlmanacNames::validateName($property_key); 35 + } catch (Exception $ex) { 36 + $error = $ex->getMessage(); 37 + } 37 38 38 - $is_new = true; 39 - $title = pht('Add Property'); 40 - $save_button = pht('Add Property'); 41 - } 39 + // NOTE: If you enter an existing name, we're just treating it as an 40 + // edit operation. This might be a little confusing. 42 41 43 - if ($is_new) { 44 - $errors = array(); 45 - $property = null; 46 - 47 - $v_name = null; 48 - $e_name = true; 49 - 50 - if ($request->isFormPost()) { 51 - $name = $request->getStr('name'); 52 - if (!strlen($name)) { 53 - $e_name = pht('Required'); 54 - $errors[] = pht('You must provide a property name.'); 42 + if ($error !== null) { 43 + if ($request->isFormPost()) { 44 + // The user is creating a new property and picked a bad name. Give 45 + // them an opportunity to fix it. 46 + return $this->buildPropertyKeyResponse($cancel_uri, $error); 55 47 } else { 56 - $caught = null; 57 - try { 58 - AlmanacNames::validateName($name); 59 - } catch (Exception $ex) { 60 - $caught = $ex; 61 - } 62 - if ($caught) { 63 - $e_name = pht('Invalid'); 64 - $errors[] = $caught->getMessage(); 65 - } 66 - } 67 - 68 - if (!$errors) { 69 - $property_key = $name; 48 + // The user is editing an invalid property. 49 + return $this->newDialog() 50 + ->setTitle(pht('Invalid Property')) 51 + ->appendParagraph( 52 + pht( 53 + 'The property name "%s" is invalid. This property can not '. 54 + 'be edited.', 55 + $property_key)) 56 + ->appendParagraph($error) 57 + ->addCancelButton($cancel_uri); 70 58 } 71 59 } 72 - 73 - if ($property_key === null) { 74 - $form = id(new AphrontFormView()) 75 - ->setUser($viewer) 76 - ->appendChild( 77 - id(new AphrontFormTextControl()) 78 - ->setName('name') 79 - ->setLabel(pht('Name')) 80 - ->setValue($v_name) 81 - ->setError($e_name)); 82 - 83 - return $this->newDialog() 84 - ->setTitle($title) 85 - ->setErrors($errors) 86 - ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) 87 - ->appendForm($form) 88 - ->addSubmitButton(pht('Continue')) 89 - ->addCancelButton($cancel_uri); 90 - } 91 60 } 92 61 93 - // Make sure property key is appropriate. 94 - // TODO: It would be cleaner to put this safety check in the Editor. 95 - AlmanacNames::validateName($property_key); 96 - 97 - // If we're adding a new property, put a placeholder on the object so 98 - // that we can build a CustomField for it. 99 - if (!$object->hasAlmanacProperty($property_key)) { 100 - $temporary_property = id(new AlmanacProperty()) 101 - ->setObjectPHID($object->getPHID()) 102 - ->setFieldName($property_key); 103 - 104 - $object->attachAlmanacProperties(array($temporary_property)); 105 - } 106 - 107 - $field_list = PhabricatorCustomField::getObjectFields( 108 - $object, 109 - PhabricatorCustomField::ROLE_DEFAULT); 110 - 111 - // Select only the field being edited. 112 - $fields = $field_list->getFields(); 113 - $fields = array_select_keys($fields, array($property_key)); 114 - $field_list = new PhabricatorCustomFieldList($fields); 115 - 116 - $field_list 117 - ->setViewer($viewer) 118 - ->readFieldsFromStorage($object); 119 - 120 - $validation_exception = null; 121 - if ($request->isFormPost() && $request->getStr('isValueEdit')) { 122 - $xactions = $field_list->buildFieldTransactionsFromRequest( 123 - $object->getApplicationTransactionTemplate(), 124 - $request); 62 + return $object->newAlmanacPropertyEditEngine() 63 + ->addContextParameter('objectPHID') 64 + ->addContextParameter('key') 65 + ->setTargetObject($object) 66 + ->setPropertyKey($property_key) 67 + ->setController($this) 68 + ->buildResponse(); 69 + } 125 70 126 - $editor = $object->getApplicationTransactionEditor() 127 - ->setActor($viewer) 128 - ->setContentSourceFromRequest($request) 129 - ->setContinueOnNoEffect(true) 130 - ->setContinueOnMissingFields(true); 71 + private function buildPropertyKeyResponse($cancel_uri, $error) { 72 + $viewer = $this->getViewer(); 73 + $request = $this->getRequest(); 74 + $v_key = $request->getStr('key'); 131 75 132 - try { 133 - $editor->applyTransactions($object, $xactions); 134 - return id(new AphrontRedirectResponse())->setURI($cancel_uri); 135 - } catch (PhabricatorApplicationTransactionValidationException $ex) { 136 - $validation_exception = $ex; 137 - } 76 + if ($error !== null) { 77 + $e_key = pht('Invalid'); 78 + } else { 79 + $e_key = true; 138 80 } 139 81 140 82 $form = id(new AphrontFormView()) 141 83 ->setUser($viewer) 142 - ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) 143 - ->addHiddenInput('key', $request->getStr('key')) 144 - ->addHiddenInput('name', $property_key) 145 - ->addHiddenInput('isValueEdit', true); 84 + ->appendChild( 85 + id(new AphrontFormTextControl()) 86 + ->setName('key') 87 + ->setLabel(pht('Name')) 88 + ->setValue($v_key) 89 + ->setError($e_key)); 146 90 147 - $field_list->appendFieldsToForm($form); 91 + $errors = array(); 92 + if ($error !== null) { 93 + $errors[] = $error; 94 + } 148 95 149 96 return $this->newDialog() 150 - ->setTitle($title) 151 - ->setValidationException($validation_exception) 97 + ->setTitle(pht('Add Property')) 98 + ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) 99 + ->setErrors($errors) 152 100 ->appendForm($form) 153 - ->addSubmitButton($save_button) 101 + ->addSubmitButton(pht('Continue')) 154 102 ->addCancelButton($cancel_uri); 155 103 } 156 104
+10
src/applications/almanac/controller/AlmanacServiceController.php
··· 11 11 return $crumbs; 12 12 } 13 13 14 + protected function getPropertyDeleteURI($object) { 15 + $id = $object->getID(); 16 + return "/almanac/service/delete/{$id}/"; 17 + } 18 + 19 + protected function getPropertyUpdateURI($object) { 20 + $id = $object->getID(); 21 + return "/almanac/service/property/{$id}/"; 22 + } 23 + 14 24 }
-80
src/applications/almanac/customfield/AlmanacCoreCustomField.php
··· 1 - <?php 2 - 3 - final class AlmanacCoreCustomField 4 - extends AlmanacCustomField 5 - implements PhabricatorStandardCustomFieldInterface { 6 - 7 - public function getStandardCustomFieldNamespace() { 8 - return 'almanac:core'; 9 - } 10 - 11 - public function getFieldKey() { 12 - return $this->getProxy()->getRawStandardFieldKey(); 13 - } 14 - 15 - public function getFieldName() { 16 - return $this->getFieldKey(); 17 - } 18 - 19 - public function createFields($object) { 20 - if (!$object->getID()) { 21 - return array(); 22 - } 23 - 24 - $specs = $object->getAlmanacPropertyFieldSpecifications(); 25 - 26 - $default_specs = array(); 27 - foreach ($object->getAlmanacProperties() as $property) { 28 - $default_specs[$property->getFieldName()] = array( 29 - 'name' => $property->getFieldName(), 30 - 'type' => 'text', 31 - ); 32 - } 33 - 34 - return PhabricatorStandardCustomField::buildStandardFields( 35 - $this, 36 - $specs + $default_specs); 37 - } 38 - 39 - public function shouldUseStorage() { 40 - return false; 41 - } 42 - 43 - public function readValueFromObject(PhabricatorCustomFieldInterface $object) { 44 - $key = $this->getFieldKey(); 45 - 46 - if ($object->hasAlmanacProperty($key)) { 47 - $this->setValueFromStorage($object->getAlmanacPropertyValue($key)); 48 - } 49 - } 50 - 51 - public function applyApplicationTransactionInternalEffects( 52 - PhabricatorApplicationTransaction $xaction) { 53 - return; 54 - } 55 - 56 - public function applyApplicationTransactionExternalEffects( 57 - PhabricatorApplicationTransaction $xaction) { 58 - 59 - $object = $this->getObject(); 60 - $phid = $object->getPHID(); 61 - $key = $this->getFieldKey(); 62 - 63 - $property = id(new AlmanacPropertyQuery()) 64 - ->setViewer($this->getViewer()) 65 - ->withObjectPHIDs(array($phid)) 66 - ->withNames(array($key)) 67 - ->executeOne(); 68 - if (!$property) { 69 - $property = id(new AlmanacProperty()) 70 - ->setObjectPHID($phid) 71 - ->setFieldIndex(PhabricatorHash::digestForIndex($key)) 72 - ->setFieldName($key); 73 - } 74 - 75 - $property 76 - ->setFieldValue($xaction->getNewValue()) 77 - ->save(); 78 - } 79 - 80 - }
-4
src/applications/almanac/customfield/AlmanacCustomField.php
··· 1 - <?php 2 - 3 - abstract class AlmanacCustomField 4 - extends PhabricatorCustomField {}
+1 -5
src/applications/almanac/editor/AlmanacBindingEditor.php
··· 1 1 <?php 2 2 3 3 final class AlmanacBindingEditor 4 - extends PhabricatorApplicationTransactionEditor { 5 - 6 - public function getEditorApplicationClass() { 7 - return 'PhabricatorAlmanacApplication'; 8 - } 4 + extends AlmanacEditor { 9 5 10 6 public function getEditorObjectsDescription() { 11 7 return pht('Almanac Binding');
+16
src/applications/almanac/editor/AlmanacBindingPropertyEditEngine.php
··· 1 + <?php 2 + 3 + final class AlmanacBindingPropertyEditEngine 4 + extends AlmanacPropertyEditEngine { 5 + 6 + const ENGINECONST = 'almanac.binding.property'; 7 + 8 + protected function newObjectQuery() { 9 + return new AlmanacBindingQuery(); 10 + } 11 + 12 + protected function getObjectViewURI($object) { 13 + return $object->getURI(); 14 + } 15 + 16 + }
+1 -9
src/applications/almanac/editor/AlmanacDeviceEditor.php
··· 1 1 <?php 2 2 3 3 final class AlmanacDeviceEditor 4 - extends PhabricatorApplicationTransactionEditor { 5 - 6 - public function getEditorApplicationClass() { 7 - return 'PhabricatorAlmanacApplication'; 8 - } 4 + extends AlmanacEditor { 9 5 10 6 public function getEditorObjectsDescription() { 11 7 return pht('Almanac Device'); 12 - } 13 - 14 - protected function supportsSearch() { 15 - return true; 16 8 } 17 9 18 10 public function getTransactionTypes() {
+16
src/applications/almanac/editor/AlmanacDevicePropertyEditEngine.php
··· 1 + <?php 2 + 3 + final class AlmanacDevicePropertyEditEngine 4 + extends AlmanacPropertyEditEngine { 5 + 6 + const ENGINECONST = 'almanac.device.property'; 7 + 8 + protected function newObjectQuery() { 9 + return new AlmanacDeviceQuery(); 10 + } 11 + 12 + protected function getObjectViewURI($object) { 13 + return $object->getURI(); 14 + } 15 + 16 + }
+156
src/applications/almanac/editor/AlmanacEditor.php
··· 1 + <?php 2 + 3 + abstract class AlmanacEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorAlmanacApplication'; 8 + } 9 + 10 + protected function supportsSearch() { 11 + return true; 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = AlmanacTransaction::TYPE_PROPERTY_UPDATE; 18 + $types[] = AlmanacTransaction::TYPE_PROPERTY_REMOVE; 19 + 20 + return $types; 21 + } 22 + 23 + protected function getCustomTransactionOldValue( 24 + PhabricatorLiskDAO $object, 25 + PhabricatorApplicationTransaction $xaction) { 26 + switch ($xaction->getTransactionType()) { 27 + case AlmanacTransaction::TYPE_PROPERTY_UPDATE: 28 + case AlmanacTransaction::TYPE_PROPERTY_REMOVE: 29 + $property_key = $xaction->getMetadataValue('almanac.property'); 30 + $exists = $object->hasAlmanacProperty($property_key); 31 + $value = $object->getAlmanacPropertyValue($property_key); 32 + return array( 33 + 'existed' => $exists, 34 + 'value' => $value, 35 + ); 36 + } 37 + 38 + return parent::getCustomTransactionOldValue($object, $xaction); 39 + } 40 + 41 + protected function getCustomTransactionNewValue( 42 + PhabricatorLiskDAO $object, 43 + PhabricatorApplicationTransaction $xaction) { 44 + 45 + switch ($xaction->getTransactionType()) { 46 + case AlmanacTransaction::TYPE_PROPERTY_UPDATE: 47 + case AlmanacTransaction::TYPE_PROPERTY_REMOVE: 48 + return $xaction->getNewValue(); 49 + } 50 + 51 + return parent::getCustomTransactionNewValue($object, $xaction); 52 + } 53 + 54 + protected function applyCustomInternalTransaction( 55 + PhabricatorLiskDAO $object, 56 + PhabricatorApplicationTransaction $xaction) { 57 + 58 + switch ($xaction->getTransactionType()) { 59 + case AlmanacTransaction::TYPE_PROPERTY_UPDATE: 60 + case AlmanacTransaction::TYPE_PROPERTY_REMOVE: 61 + return; 62 + } 63 + 64 + return parent::applyCustomInternalTransaction($object, $xaction); 65 + } 66 + 67 + protected function applyCustomExternalTransaction( 68 + PhabricatorLiskDAO $object, 69 + PhabricatorApplicationTransaction $xaction) { 70 + 71 + switch ($xaction->getTransactionType()) { 72 + case AlmanacTransaction::TYPE_PROPERTY_UPDATE: 73 + $property_key = $xaction->getMetadataValue('almanac.property'); 74 + if ($object->hasAlmanacProperty($property_key)) { 75 + $property = $object->getAlmanacProperty($property_key); 76 + } else { 77 + $property = id(new AlmanacProperty()) 78 + ->setObjectPHID($object->getPHID()) 79 + ->setFieldName($property_key); 80 + } 81 + $property 82 + ->setFieldValue($xaction->getNewValue()) 83 + ->save(); 84 + return; 85 + case AlmanacTransaction::TYPE_PROPERTY_REMOVE: 86 + $property_key = $xaction->getMetadataValue('almanac.property'); 87 + if ($object->hasAlmanacProperty($property_key)) { 88 + $property = $object->getAlmanacProperty($property_key); 89 + $property->delete(); 90 + } 91 + return; 92 + } 93 + 94 + return parent::applyCustomExternalTransaction($object, $xaction); 95 + } 96 + 97 + protected function validateTransaction( 98 + PhabricatorLiskDAO $object, 99 + $type, 100 + array $xactions) { 101 + 102 + $errors = parent::validateTransaction($object, $type, $xactions); 103 + 104 + switch ($type) { 105 + case AlmanacTransaction::TYPE_PROPERTY_UPDATE: 106 + foreach ($xactions as $xaction) { 107 + $property_key = $xaction->getMetadataValue('almanac.property'); 108 + 109 + $message = null; 110 + try { 111 + AlmanacNames::validateName($property_key); 112 + } catch (Exception $ex) { 113 + $message = $ex->getMessage(); 114 + } 115 + 116 + if ($message !== null) { 117 + $error = new PhabricatorApplicationTransactionValidationError( 118 + $type, 119 + pht('Invalid'), 120 + $message, 121 + $xaction); 122 + $errors[] = $error; 123 + continue; 124 + } 125 + 126 + $new_value = $xaction->getNewValue(); 127 + try { 128 + phutil_json_encode($new_value); 129 + } catch (Exception $ex) { 130 + $message = pht( 131 + 'Almanac property values must be representable in JSON. %s', 132 + $ex->getMessage()); 133 + } 134 + 135 + if ($message !== null) { 136 + $error = new PhabricatorApplicationTransactionValidationError( 137 + $type, 138 + pht('Invalid'), 139 + $message, 140 + $xaction); 141 + $errors[] = $error; 142 + continue; 143 + } 144 + } 145 + break; 146 + 147 + case AlmanacTransaction::TYPE_PROPERTY_REMOVE: 148 + // NOTE: No name validation on removals since it's OK to delete 149 + // an invalid property that somehow came into existence. 150 + break; 151 + } 152 + 153 + return $errors; 154 + } 155 + 156 + }
+79
src/applications/almanac/editor/AlmanacPropertyEditEngine.php
··· 1 + <?php 2 + 3 + abstract class AlmanacPropertyEditEngine 4 + extends PhabricatorEditEngine { 5 + 6 + private $propertyKey; 7 + 8 + public function setPropertyKey($property_key) { 9 + $this->propertyKey = $property_key; 10 + return $this; 11 + } 12 + 13 + public function getPropertyKey() { 14 + return $this->propertyKey; 15 + } 16 + 17 + public function isEngineConfigurable() { 18 + return false; 19 + } 20 + 21 + public function isEngineExtensible() { 22 + return false; 23 + } 24 + 25 + public function getEngineName() { 26 + return pht('Almanac Properties'); 27 + } 28 + 29 + public function getSummaryHeader() { 30 + return pht('Edit Almanac Property Configurations'); 31 + } 32 + 33 + public function getSummaryText() { 34 + return pht('This engine is used to edit Almanac properties.'); 35 + } 36 + 37 + public function getEngineApplicationClass() { 38 + return 'PhabricatorAlmanacApplication'; 39 + } 40 + 41 + protected function newEditableObject() { 42 + throw new PhutilMethodNotImplementedException(); 43 + } 44 + 45 + protected function getObjectCreateTitleText($object) { 46 + return pht('Create Property'); 47 + } 48 + 49 + protected function getObjectCreateButtonText($object) { 50 + return pht('Create Property'); 51 + } 52 + 53 + protected function getObjectEditTitleText($object) { 54 + return pht('Edit Property: %s', $object->getName()); 55 + } 56 + 57 + protected function getObjectEditShortText($object) { 58 + return pht('Edit Property'); 59 + } 60 + 61 + protected function getObjectCreateShortText() { 62 + return pht('Create Property'); 63 + } 64 + 65 + protected function buildCustomEditFields($object) { 66 + $property_key = $this->getPropertyKey(); 67 + $xaction_type = AlmanacTransaction::TYPE_PROPERTY_UPDATE; 68 + 69 + return array( 70 + id(new PhabricatorTextEditField()) 71 + ->setKey('value') 72 + ->setMetadataValue('almanac.property', $property_key) 73 + ->setLabel($property_key) 74 + ->setTransactionType($xaction_type) 75 + ->setValue($object->getAlmanacPropertyValue($property_key)), 76 + ); 77 + } 78 + 79 + }
+1 -11
src/applications/almanac/editor/AlmanacServiceEditor.php
··· 1 1 <?php 2 2 3 3 final class AlmanacServiceEditor 4 - extends PhabricatorApplicationTransactionEditor { 5 - 6 - public function getEditorApplicationClass() { 7 - return 'PhabricatorAlmanacApplication'; 8 - } 4 + extends AlmanacEditor { 9 5 10 6 public function getEditorObjectsDescription() { 11 7 return pht('Almanac Service'); 12 - } 13 - 14 - protected function supportsSearch() { 15 - return true; 16 8 } 17 9 18 10 public function getTransactionTypes() { ··· 184 176 185 177 return $errors; 186 178 } 187 - 188 - 189 179 190 180 }
+16
src/applications/almanac/editor/AlmanacServicePropertyEditEngine.php
··· 1 + <?php 2 + 3 + final class AlmanacServicePropertyEditEngine 4 + extends AlmanacPropertyEditEngine { 5 + 6 + const ENGINECONST = 'almanac.service.property'; 7 + 8 + protected function newObjectQuery() { 9 + return new AlmanacServiceQuery(); 10 + } 11 + 12 + protected function getObjectViewURI($object) { 13 + return $object->getURI(); 14 + } 15 + 16 + }
+1
src/applications/almanac/property/AlmanacPropertyInterface.php
··· 8 8 public function getAlmanacProperty($key); 9 9 public function getAlmanacPropertyValue($key, $default = null); 10 10 public function getAlmanacPropertyFieldSpecifications(); 11 + public function newAlmanacPropertyEditEngine(); 11 12 12 13 }
+16 -3
src/applications/almanac/query/AlmanacQuery.php
··· 5 5 6 6 protected function didFilterPage(array $objects) { 7 7 if (head($objects) instanceof AlmanacPropertyInterface) { 8 - // NOTE: We load properties unconditionally because CustomField assumes 9 - // it can always generate a list of fields on an object. It may make 10 - // sense to re-examine that assumption eventually. 8 + // NOTE: We load properties for obsolete historical reasons. It may make 9 + // sense to re-examine that assumption shortly. 11 10 12 11 $property_query = id(new AlmanacPropertyQuery()) 13 12 ->setViewer($this->getViewer()) ··· 25 24 $properties = mgroup($properties, 'getObjectPHID'); 26 25 foreach ($objects as $object) { 27 26 $object_properties = idx($properties, $object->getPHID(), array()); 27 + $object_properties = mpull($object_properties, null, 'getFieldName'); 28 + 29 + // Create synthetic properties for defaults on the object itself. 30 + $specs = $object->getAlmanacPropertyFieldSpecifications(); 31 + foreach ($specs as $key => $spec) { 32 + if (empty($object_properties[$key])) { 33 + $object_properties[$key] = id(new AlmanacProperty()) 34 + ->setObjectPHID($object->getPHID()) 35 + ->setFieldName($key) 36 + ->setFieldValue($spec->getValueForTransaction()); 37 + } 38 + } 39 + 28 40 foreach ($object_properties as $property) { 29 41 $property->attachObject($object); 30 42 } 43 + 31 44 $object->attachAlmanacProperties($object_properties); 32 45 } 33 46 }
-4
src/applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php
··· 16 16 'Defines a database service for use in a Phabricator cluster.'); 17 17 } 18 18 19 - public function getFieldSpecifications() { 20 - return array(); 21 - } 22 - 23 19 }
+1 -10
src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php
··· 18 18 19 19 public function getFieldSpecifications() { 20 20 return array( 21 - 'closed' => array( 22 - 'type' => 'bool', 23 - 'name' => pht('Closed'), 24 - 'default' => false, 25 - 'strings' => array( 26 - 'edit.checkbox' => pht( 27 - 'Prevent new repositories from being allocated on this '. 28 - 'service.'), 29 - ), 30 - ), 21 + 'closed' => id(new PhabricatorTextEditField()), 31 22 ); 32 23 } 33 24
+8 -23
src/applications/almanac/storage/AlmanacBinding.php
··· 4 4 extends AlmanacDAO 5 5 implements 6 6 PhabricatorPolicyInterface, 7 - PhabricatorCustomFieldInterface, 8 7 PhabricatorApplicationTransactionInterface, 9 8 AlmanacPropertyInterface, 10 9 PhabricatorDestructibleInterface { ··· 17 16 private $service = self::ATTACHABLE; 18 17 private $device = self::ATTACHABLE; 19 18 private $interface = self::ATTACHABLE; 20 - private $customFields = self::ATTACHABLE; 21 19 private $almanacProperties = self::ATTACHABLE; 22 20 23 21 public static function initializeNewBinding(AlmanacService $service) { ··· 56 54 $this->mailKey = Filesystem::readRandomCharacters(20); 57 55 } 58 56 return parent::save(); 57 + } 58 + 59 + public function getName() { 60 + return pht('Binding %s', $this->getID()); 59 61 } 60 62 61 63 public function getURI() { ··· 124 126 return array(); 125 127 } 126 128 129 + public function newAlmanacPropertyEditEngine() { 130 + return new AlmanacBindingPropertyEditEngine(); 131 + } 132 + 127 133 128 134 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 129 135 ··· 159 165 } 160 166 161 167 return $notes; 162 - } 163 - 164 - 165 - /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 166 - 167 - 168 - public function getCustomFieldSpecificationForRole($role) { 169 - return array(); 170 - } 171 - 172 - public function getCustomFieldBaseClass() { 173 - return 'AlmanacCustomField'; 174 - } 175 - 176 - public function getCustomFields() { 177 - return $this->assertAttached($this->customFields); 178 - } 179 - 180 - public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { 181 - $this->customFields = $fields; 182 - return $this; 183 168 } 184 169 185 170
+1 -1
src/applications/almanac/storage/AlmanacBindingTransaction.php
··· 1 1 <?php 2 2 3 3 final class AlmanacBindingTransaction 4 - extends PhabricatorApplicationTransaction { 4 + extends AlmanacTransaction { 5 5 6 6 const TYPE_INTERFACE = 'almanac:binding:interface'; 7 7
+4 -23
src/applications/almanac/storage/AlmanacDevice.php
··· 4 4 extends AlmanacDAO 5 5 implements 6 6 PhabricatorPolicyInterface, 7 - PhabricatorCustomFieldInterface, 8 7 PhabricatorApplicationTransactionInterface, 9 8 PhabricatorProjectInterface, 10 9 PhabricatorSSHPublicKeyInterface, ··· 19 18 protected $editPolicy; 20 19 protected $isLocked; 21 20 22 - private $customFields = self::ATTACHABLE; 23 21 private $almanacProperties = self::ATTACHABLE; 24 22 25 23 public static function initializeNewDevice() { ··· 137 135 return array(); 138 136 } 139 137 138 + public function newAlmanacPropertyEditEngine() { 139 + return new AlmanacDevicePropertyEditEngine(); 140 + } 141 + 140 142 141 143 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 142 144 ··· 175 177 } 176 178 177 179 return null; 178 - } 179 - 180 - 181 - /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 182 - 183 - 184 - public function getCustomFieldSpecificationForRole($role) { 185 - return array(); 186 - } 187 - 188 - public function getCustomFieldBaseClass() { 189 - return 'AlmanacCustomField'; 190 - } 191 - 192 - public function getCustomFields() { 193 - return $this->assertAttached($this->customFields); 194 - } 195 - 196 - public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { 197 - $this->customFields = $fields; 198 - return $this; 199 180 } 200 181 201 182
+1 -1
src/applications/almanac/storage/AlmanacDeviceTransaction.php
··· 1 1 <?php 2 2 3 3 final class AlmanacDeviceTransaction 4 - extends PhabricatorApplicationTransaction { 4 + extends AlmanacTransaction { 5 5 6 6 const TYPE_NAME = 'almanac:device:name'; 7 7 const TYPE_INTERFACE = 'almanac:device:interface';
+4 -23
src/applications/almanac/storage/AlmanacNamespace.php
··· 4 4 extends AlmanacDAO 5 5 implements 6 6 PhabricatorPolicyInterface, 7 - PhabricatorCustomFieldInterface, 8 7 PhabricatorApplicationTransactionInterface, 9 8 PhabricatorProjectInterface, 10 9 AlmanacPropertyInterface, ··· 17 16 protected $viewPolicy; 18 17 protected $editPolicy; 19 18 20 - private $customFields = self::ATTACHABLE; 21 19 private $almanacProperties = self::ATTACHABLE; 22 20 23 21 public static function initializeNewNamespace() { ··· 148 146 return array(); 149 147 } 150 148 149 + public function newAlmanacPropertyEditEngine() { 150 + throw new PhutilMethodNotImplementedException(); 151 + } 152 + 151 153 152 154 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 153 155 ··· 174 176 175 177 public function describeAutomaticCapability($capability) { 176 178 return null; 177 - } 178 - 179 - 180 - /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 181 - 182 - 183 - public function getCustomFieldSpecificationForRole($role) { 184 - return array(); 185 - } 186 - 187 - public function getCustomFieldBaseClass() { 188 - return 'AlmanacCustomField'; 189 - } 190 - 191 - public function getCustomFields() { 192 - return $this->assertAttached($this->customFields); 193 - } 194 - 195 - public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { 196 - $this->customFields = $fields; 197 - return $this; 198 179 } 199 180 200 181
+24 -42
src/applications/almanac/storage/AlmanacProperty.php
··· 1 1 <?php 2 2 3 3 final class AlmanacProperty 4 - extends PhabricatorCustomFieldStorage 4 + extends AlmanacDAO 5 5 implements PhabricatorPolicyInterface { 6 6 7 + protected $objectPHID; 8 + protected $fieldIndex; 7 9 protected $fieldName; 10 + protected $fieldValue; 8 11 9 12 private $object = self::ATTACHABLE; 10 - 11 - public function getApplicationName() { 12 - return 'almanac'; 13 - } 14 13 15 14 protected function getConfiguration() { 16 - $config = parent::getConfiguration(); 17 - 18 - $config[self::CONFIG_COLUMN_SCHEMA] += array( 19 - 'fieldName' => 'text128', 20 - ); 21 - 22 - return $config; 15 + return array( 16 + self::CONFIG_TIMESTAMPS => false, 17 + self::CONFIG_SERIALIZATION => array( 18 + 'fieldValue' => self::SERIALIZATION_JSON, 19 + ), 20 + self::CONFIG_COLUMN_SCHEMA => array( 21 + 'fieldIndex' => 'bytes12', 22 + 'fieldName' => 'text128', 23 + ), 24 + self::CONFIG_KEY_SCHEMA => array( 25 + 'objectPHID' => array( 26 + 'columns' => array('objectPHID', 'fieldIndex'), 27 + 'unique' => true, 28 + ), 29 + ), 30 + ) + parent::getConfiguration(); 23 31 } 24 32 25 33 public function getObject() { ··· 31 39 return $this; 32 40 } 33 41 34 - public static function buildTransactions( 35 - AlmanacPropertyInterface $object, 36 - array $properties) { 42 + public function save() { 43 + $hash = PhabricatorHash::digestForIndex($this->getFieldName()); 44 + $this->setFieldIndex($hash); 37 45 38 - $template = $object->getApplicationTransactionTemplate(); 39 - 40 - $attached_properties = $object->getAlmanacProperties(); 41 - foreach ($properties as $key => $value) { 42 - if (empty($attached_properties[$key])) { 43 - $attached_properties[] = id(new AlmanacProperty()) 44 - ->setObjectPHID($object->getPHID()) 45 - ->setFieldName($key); 46 - } 47 - } 48 - $object->attachAlmanacProperties($attached_properties); 49 - 50 - $field_list = PhabricatorCustomField::getObjectFields( 51 - $object, 52 - PhabricatorCustomField::ROLE_DEFAULT); 53 - $fields = $field_list->getFields(); 54 - 55 - $xactions = array(); 56 - foreach ($properties as $name => $property) { 57 - $xactions[] = id(clone $template) 58 - ->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD) 59 - ->setMetadataValue('customfield:key', $name) 60 - ->setOldValue($object->getAlmanacPropertyValue($name)) 61 - ->setNewValue($property); 62 - } 63 - 64 - return $xactions; 46 + return parent::save(); 65 47 } 66 48 67 49
+4 -23
src/applications/almanac/storage/AlmanacService.php
··· 4 4 extends AlmanacDAO 5 5 implements 6 6 PhabricatorPolicyInterface, 7 - PhabricatorCustomFieldInterface, 8 7 PhabricatorApplicationTransactionInterface, 9 8 PhabricatorProjectInterface, 10 9 AlmanacPropertyInterface, ··· 19 18 protected $serviceClass; 20 19 protected $isLocked; 21 20 22 - private $customFields = self::ATTACHABLE; 23 21 private $almanacProperties = self::ATTACHABLE; 24 22 private $bindings = self::ATTACHABLE; 25 23 private $serviceType = self::ATTACHABLE; ··· 130 128 return $this->getServiceType()->getFieldSpecifications(); 131 129 } 132 130 131 + public function newAlmanacPropertyEditEngine() { 132 + return new AlmanacServicePropertyEditEngine(); 133 + } 134 + 133 135 134 136 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 135 137 ··· 168 170 } 169 171 170 172 return null; 171 - } 172 - 173 - 174 - /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 175 - 176 - 177 - public function getCustomFieldSpecificationForRole($role) { 178 - return array(); 179 - } 180 - 181 - public function getCustomFieldBaseClass() { 182 - return 'AlmanacCustomField'; 183 - } 184 - 185 - public function getCustomFields() { 186 - return $this->assertAttached($this->customFields); 187 - } 188 - 189 - public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { 190 - $this->customFields = $fields; 191 - return $this; 192 173 } 193 174 194 175
+1 -9
src/applications/almanac/storage/AlmanacServiceTransaction.php
··· 1 1 <?php 2 2 3 3 final class AlmanacServiceTransaction 4 - extends PhabricatorApplicationTransaction { 4 + extends AlmanacTransaction { 5 5 6 6 const TYPE_NAME = 'almanac:service:name'; 7 7 const TYPE_LOCK = 'almanac:service:lock'; 8 8 9 - public function getApplicationName() { 10 - return 'almanac'; 11 - } 12 - 13 9 public function getApplicationTransactionType() { 14 10 return AlmanacServicePHIDType::TYPECONST; 15 - } 16 - 17 - public function getApplicationTransactionCommentObject() { 18 - return null; 19 11 } 20 12 21 13 public function getTitle() {
+38
src/applications/almanac/storage/AlmanacTransaction.php
··· 1 + <?php 2 + 3 + abstract class AlmanacTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_PROPERTY_UPDATE = 'almanac:property:update'; 7 + const TYPE_PROPERTY_REMOVE = 'almanac:property:remove'; 8 + 9 + public function getApplicationName() { 10 + return 'almanac'; 11 + } 12 + 13 + public function getApplicationTransactionCommentObject() { 14 + return null; 15 + } 16 + 17 + public function getTitle() { 18 + $author_phid = $this->getAuthorPHID(); 19 + 20 + switch ($this->getTransactionType()) { 21 + case self::TYPE_PROPERTY_UPDATE: 22 + $property_key = $this->getMetadataValue('almanac.property'); 23 + return pht( 24 + '%s updated the property "%s".', 25 + $this->renderHandleLink($author_phid), 26 + $property_key); 27 + case self::TYPE_PROPERTY_REMOVE: 28 + $property_key = $this->getMetadataValue('almanac.property'); 29 + return pht( 30 + '%s deleted the property "%s".', 31 + $this->renderHandleLink($author_phid), 32 + $property_key); 33 + } 34 + 35 + return parent::getTitle(); 36 + } 37 + 38 + }
+45 -17
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 23 23 private $isCreate; 24 24 private $editEngineConfiguration; 25 25 private $contextParameters = array(); 26 + private $targetObject; 26 27 27 28 final public function setViewer(PhabricatorUser $viewer) { 28 29 $this->viewer = $viewer; ··· 61 62 return true; 62 63 } 63 64 65 + public function isEngineExtensible() { 66 + return true; 67 + } 68 + 69 + /** 70 + * Force the engine to edit a particular object. 71 + */ 72 + public function setTargetObject($target_object) { 73 + $this->targetObject = $target_object; 74 + return $this; 75 + } 76 + 77 + public function getTargetObject() { 78 + return $this->targetObject; 79 + } 80 + 64 81 65 82 /* -( Managing Fields )---------------------------------------------------- */ 66 83 ··· 94 111 95 112 $fields = mpull($fields, null, 'getKey'); 96 113 97 - $extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions(); 114 + if ($this->isEngineExtensible()) { 115 + $extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions(); 116 + } else { 117 + $extensions = array(); 118 + } 119 + 98 120 foreach ($extensions as $extension) { 99 121 $extension->setViewer($viewer); 100 122 ··· 720 742 break; 721 743 } 722 744 723 - $id = $request->getURIData('id'); 745 + $object = $this->getTargetObject(); 746 + if (!$object) { 747 + $id = $request->getURIData('id'); 748 + 749 + if ($id) { 750 + $this->setIsCreate(false); 751 + $object = $this->newObjectFromID($id, $capabilities); 752 + if (!$object) { 753 + return new Aphront404Response(); 754 + } 755 + } else { 756 + // Make sure the viewer has permission to create new objects of 757 + // this type if we're going to create a new object. 758 + if ($require_create) { 759 + $this->requireCreateCapability(); 760 + } 724 761 725 - if ($id) { 726 - $this->setIsCreate(false); 727 - $object = $this->newObjectFromID($id, $capabilities); 728 - if (!$object) { 729 - return new Aphront404Response(); 762 + $this->setIsCreate(true); 763 + $object = $this->newEditableObject(); 730 764 } 731 765 } else { 732 - // Make sure the viewer has permission to create new objects of 733 - // this type if we're going to create a new object. 734 - if ($require_create) { 735 - $this->requireCreateCapability(); 736 - } 737 - 738 - $this->setIsCreate(true); 739 - $object = $this->newEditableObject(); 766 + $id = $object->getID(); 740 767 } 741 768 742 769 $this->validateObject($object); ··· 831 858 $template = $object->getApplicationTransactionTemplate(); 832 859 833 860 $validation_exception = null; 834 - if ($request->isFormPost()) { 861 + if ($request->isFormPost() && $request->getBool('editEngine')) { 835 862 $submit_fields = $fields; 836 863 837 864 foreach ($submit_fields as $key => $field) { ··· 1044 1071 $request = $controller->getRequest(); 1045 1072 1046 1073 $form = id(new AphrontFormView()) 1047 - ->setUser($viewer); 1074 + ->setUser($viewer) 1075 + ->addHiddenInput('editEngine', 'true'); 1048 1076 1049 1077 foreach ($this->contextParameters as $param) { 1050 1078 $form->addHiddenInput($param, $request->getStr($param));
+7 -1
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 456 456 return null; 457 457 } 458 458 459 + $object = $this->getObject(); 460 + 461 + if (!($object instanceof PhabricatorCustomFieldInterface)) { 462 + return null; 463 + } 464 + 459 465 $field = PhabricatorCustomField::getObjectField( 460 - $this->getObject(), 466 + $object, 461 467 PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS, 462 468 $key); 463 469 if (!$field) {