diff --git a/resources/sql/autopatches/20160222.almanac.1.properties.php b/resources/sql/autopatches/20160222.almanac.1.properties.php new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20160222.almanac.1.properties.php @@ -0,0 +1,28 @@ +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()); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -14,6 +14,7 @@ '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 @@ '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 @@ '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 @@ '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 @@ '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 @@ '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 @@ '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 @@ '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 @@ ), '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 @@ 'AlmanacNamespace' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', - 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorProjectInterface', 'AlmanacPropertyInterface', @@ -4104,12 +4103,13 @@ '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 @@ 'AlmanacService' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', - 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorProjectInterface', 'AlmanacPropertyInterface', @@ -4128,17 +4127,19 @@ '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', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -43,12 +43,12 @@ return array( '/almanac/' => array( '' => 'AlmanacConsoleController', - 'service/' => array( + '(?Pservice)/' => array( $this->getQueryRoutePattern() => 'AlmanacServiceListController', 'edit/(?:(?P\d+)/)?' => 'AlmanacServiceEditController', 'view/(?P[^/]+)/' => 'AlmanacServiceViewController', ), - 'device/' => array( + '(?Pdevice)/' => array( $this->getQueryRoutePattern() => 'AlmanacDeviceListController', 'edit/(?:(?P\d+)/)?' => 'AlmanacDeviceEditController', 'view/(?P[^/]+)/' => 'AlmanacDeviceViewController', @@ -65,16 +65,16 @@ 'edit/(?:(?P\d+)/)?' => 'AlmanacNetworkEditController', '(?P\d+)/' => 'AlmanacNetworkViewController', ), - 'property/' => array( - 'edit/' => 'AlmanacPropertyEditController', - 'delete/' => 'AlmanacPropertyDeleteController', - ), 'namespace/' => array( $this->getQueryRoutePattern() => 'AlmanacNamespaceListController', $this->getEditRoutePattern('edit/') => 'AlmanacNamespaceEditController', '(?P\d+)/' => 'AlmanacNamespaceViewController', ), + 'property/' => array( + 'delete/' => 'AlmanacPropertyDeleteController', + 'update/' => 'AlmanacPropertyEditController', + ), ), ); } diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php --- a/src/applications/almanac/controller/AlmanacController.php +++ b/src/applications/almanac/controller/AlmanacController.php @@ -10,27 +10,14 @@ $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 @@ )); $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 @@ )); $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 @@ $box->setInfoView($error_view); } + protected function getPropertyDeleteURI($object) { + return null; + } + + protected function getPropertyUpdateURI($object) { + return null; + } + } diff --git a/src/applications/almanac/controller/AlmanacPropertyDeleteController.php b/src/applications/almanac/controller/AlmanacPropertyDeleteController.php --- a/src/applications/almanac/controller/AlmanacPropertyDeleteController.php +++ b/src/applications/almanac/controller/AlmanacPropertyDeleteController.php @@ -34,53 +34,24 @@ $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) diff --git a/src/applications/almanac/controller/AlmanacPropertyEditController.php b/src/applications/almanac/controller/AlmanacPropertyEditController.php --- a/src/applications/almanac/controller/AlmanacPropertyEditController.php +++ b/src/applications/almanac/controller/AlmanacPropertyEditController.php @@ -24,133 +24,81 @@ } $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; + $error = null; + try { + AlmanacNames::validateName($property_key); + } catch (Exception $ex) { + $error = $ex->getMessage(); + } - $v_name = null; - $e_name = true; + // NOTE: If you enter an existing name, we're just treating it as an + // edit operation. This might be a little confusing. - if ($request->isFormPost()) { - $name = $request->getStr('name'); - if (!strlen($name)) { - $e_name = pht('Required'); - $errors[] = pht('You must provide a property name.'); + 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 { - $caught = null; - try { - AlmanacNames::validateName($name); - } catch (Exception $ex) { - $caught = $ex; - } - if ($caught) { - $e_name = pht('Invalid'); - $errors[] = $caught->getMessage(); - } - } - - if (!$errors) { - $property_key = $name; + // 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 ($property_key === null) { - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($v_name) - ->setError($e_name)); - - return $this->newDialog() - ->setTitle($title) - ->setErrors($errors) - ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) - ->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); + return $object->newAlmanacPropertyEditEngine() + ->addContextParameter('objectPHID') + ->addContextParameter('key') + ->setTargetObject($object) + ->setPropertyKey($property_key) + ->setController($this) + ->buildResponse(); + } - $editor = $object->getApplicationTransactionEditor() - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->setContinueOnMissingFields(true); + private function buildPropertyKeyResponse($cancel_uri, $error) { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + $v_key = $request->getStr('key'); - try { - $editor->applyTransactions($object, $xactions); - return id(new AphrontRedirectResponse())->setURI($cancel_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - } + if ($error !== null) { + $e_key = pht('Invalid'); + } else { + $e_key = true; } $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); + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('key') + ->setLabel(pht('Name')) + ->setValue($v_key) + ->setError($e_key)); + + $errors = array(); + if ($error !== null) { + $errors[] = $error; + } return $this->newDialog() - ->setTitle($title) - ->setValidationException($validation_exception) + ->setTitle(pht('Add Property')) + ->addHiddenInput('objectPHID', $request->getStr('objectPHID')) + ->setErrors($errors) ->appendForm($form) - ->addSubmitButton($save_button) + ->addSubmitButton(pht('Continue')) ->addCancelButton($cancel_uri); } diff --git a/src/applications/almanac/controller/AlmanacServiceController.php b/src/applications/almanac/controller/AlmanacServiceController.php --- a/src/applications/almanac/controller/AlmanacServiceController.php +++ b/src/applications/almanac/controller/AlmanacServiceController.php @@ -11,4 +11,14 @@ 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}/"; + } + } diff --git a/src/applications/almanac/customfield/AlmanacCoreCustomField.php b/src/applications/almanac/customfield/AlmanacCoreCustomField.php deleted file mode 100644 --- a/src/applications/almanac/customfield/AlmanacCoreCustomField.php +++ /dev/null @@ -1,80 +0,0 @@ -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(); - } - -} diff --git a/src/applications/almanac/customfield/AlmanacCustomField.php b/src/applications/almanac/customfield/AlmanacCustomField.php deleted file mode 100644 --- a/src/applications/almanac/customfield/AlmanacCustomField.php +++ /dev/null @@ -1,4 +0,0 @@ -getURI(); + } + +} diff --git a/src/applications/almanac/editor/AlmanacDeviceEditor.php b/src/applications/almanac/editor/AlmanacDeviceEditor.php --- a/src/applications/almanac/editor/AlmanacDeviceEditor.php +++ b/src/applications/almanac/editor/AlmanacDeviceEditor.php @@ -1,20 +1,12 @@ getURI(); + } + +} diff --git a/src/applications/almanac/editor/AlmanacEditor.php b/src/applications/almanac/editor/AlmanacEditor.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/editor/AlmanacEditor.php @@ -0,0 +1,156 @@ +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; + } + +} diff --git a/src/applications/almanac/editor/AlmanacPropertyEditEngine.php b/src/applications/almanac/editor/AlmanacPropertyEditEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/editor/AlmanacPropertyEditEngine.php @@ -0,0 +1,79 @@ +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)), + ); + } + +} diff --git a/src/applications/almanac/editor/AlmanacServiceEditor.php b/src/applications/almanac/editor/AlmanacServiceEditor.php --- a/src/applications/almanac/editor/AlmanacServiceEditor.php +++ b/src/applications/almanac/editor/AlmanacServiceEditor.php @@ -1,20 +1,12 @@ getURI(); + } + +} diff --git a/src/applications/almanac/property/AlmanacPropertyInterface.php b/src/applications/almanac/property/AlmanacPropertyInterface.php --- a/src/applications/almanac/property/AlmanacPropertyInterface.php +++ b/src/applications/almanac/property/AlmanacPropertyInterface.php @@ -8,5 +8,6 @@ public function getAlmanacProperty($key); public function getAlmanacPropertyValue($key, $default = null); public function getAlmanacPropertyFieldSpecifications(); + public function newAlmanacPropertyEditEngine(); } diff --git a/src/applications/almanac/query/AlmanacQuery.php b/src/applications/almanac/query/AlmanacQuery.php --- a/src/applications/almanac/query/AlmanacQuery.php +++ b/src/applications/almanac/query/AlmanacQuery.php @@ -5,9 +5,8 @@ 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 @@ $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); } } diff --git a/src/applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php b/src/applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php --- a/src/applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php +++ b/src/applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php @@ -16,8 +16,4 @@ 'Defines a database service for use in a Phabricator cluster.'); } - public function getFieldSpecifications() { - return array(); - } - } diff --git a/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php b/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php --- a/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php +++ b/src/applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php @@ -18,16 +18,7 @@ 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()), ); } diff --git a/src/applications/almanac/storage/AlmanacBinding.php b/src/applications/almanac/storage/AlmanacBinding.php --- a/src/applications/almanac/storage/AlmanacBinding.php +++ b/src/applications/almanac/storage/AlmanacBinding.php @@ -4,7 +4,6 @@ extends AlmanacDAO implements PhabricatorPolicyInterface, - PhabricatorCustomFieldInterface, PhabricatorApplicationTransactionInterface, AlmanacPropertyInterface, PhabricatorDestructibleInterface { @@ -17,7 +16,6 @@ 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 @@ return parent::save(); } + public function getName() { + return pht('Binding %s', $this->getID()); + } + public function getURI() { return '/almanac/binding/'.$this->getID().'/'; } @@ -124,6 +126,10 @@ return array(); } + public function newAlmanacPropertyEditEngine() { + return new AlmanacBindingPropertyEditEngine(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -162,27 +168,6 @@ } -/* -( 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 )------------------------- */ diff --git a/src/applications/almanac/storage/AlmanacBindingTransaction.php b/src/applications/almanac/storage/AlmanacBindingTransaction.php --- a/src/applications/almanac/storage/AlmanacBindingTransaction.php +++ b/src/applications/almanac/storage/AlmanacBindingTransaction.php @@ -1,7 +1,7 @@ assertAttached($this->customFields); - } - - public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { - $this->customFields = $fields; - return $this; - } - - /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/almanac/storage/AlmanacDeviceTransaction.php b/src/applications/almanac/storage/AlmanacDeviceTransaction.php --- a/src/applications/almanac/storage/AlmanacDeviceTransaction.php +++ b/src/applications/almanac/storage/AlmanacDeviceTransaction.php @@ -1,7 +1,7 @@ assertAttached($this->customFields); - } - - public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { - $this->customFields = $fields; - return $this; - } - - /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/almanac/storage/AlmanacProperty.php b/src/applications/almanac/storage/AlmanacProperty.php --- a/src/applications/almanac/storage/AlmanacProperty.php +++ b/src/applications/almanac/storage/AlmanacProperty.php @@ -1,25 +1,33 @@ 'text128', - ); - - return $config; + return array( + self::CONFIG_TIMESTAMPS => false, + self::CONFIG_SERIALIZATION => array( + 'fieldValue' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'fieldIndex' => 'bytes12', + 'fieldName' => 'text128', + ), + self::CONFIG_KEY_SCHEMA => array( + 'objectPHID' => array( + 'columns' => array('objectPHID', 'fieldIndex'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); } public function getObject() { @@ -31,37 +39,11 @@ return $this; } - public static function buildTransactions( - AlmanacPropertyInterface $object, - array $properties) { - - $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; + public function save() { + $hash = PhabricatorHash::digestForIndex($this->getFieldName()); + $this->setFieldIndex($hash); + + return parent::save(); } diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php --- a/src/applications/almanac/storage/AlmanacService.php +++ b/src/applications/almanac/storage/AlmanacService.php @@ -4,7 +4,6 @@ extends AlmanacDAO implements PhabricatorPolicyInterface, - PhabricatorCustomFieldInterface, PhabricatorApplicationTransactionInterface, PhabricatorProjectInterface, AlmanacPropertyInterface, @@ -19,7 +18,6 @@ protected $serviceClass; protected $isLocked; - private $customFields = self::ATTACHABLE; private $almanacProperties = self::ATTACHABLE; private $bindings = self::ATTACHABLE; private $serviceType = self::ATTACHABLE; @@ -130,6 +128,10 @@ return $this->getServiceType()->getFieldSpecifications(); } + public function newAlmanacPropertyEditEngine() { + return new AlmanacServicePropertyEditEngine(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -171,27 +173,6 @@ } -/* -( 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 )------------------------- */ diff --git a/src/applications/almanac/storage/AlmanacServiceTransaction.php b/src/applications/almanac/storage/AlmanacServiceTransaction.php --- a/src/applications/almanac/storage/AlmanacServiceTransaction.php +++ b/src/applications/almanac/storage/AlmanacServiceTransaction.php @@ -1,23 +1,15 @@ getAuthorPHID(); diff --git a/src/applications/almanac/storage/AlmanacTransaction.php b/src/applications/almanac/storage/AlmanacTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/storage/AlmanacTransaction.php @@ -0,0 +1,38 @@ +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(); + } + +} diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -23,6 +23,7 @@ private $isCreate; private $editEngineConfiguration; private $contextParameters = array(); + private $targetObject; final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -61,6 +62,22 @@ 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 @@ $fields = mpull($fields, null, 'getKey'); - $extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions(); + if ($this->isEngineExtensible()) { + $extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions(); + } else { + $extensions = array(); + } + foreach ($extensions as $extension) { $extension->setViewer($viewer); @@ -720,23 +742,28 @@ break; } - $id = $request->getURIData('id'); + $object = $this->getTargetObject(); + if (!$object) { + $id = $request->getURIData('id'); + + if ($id) { + $this->setIsCreate(false); + $object = $this->newObjectFromID($id, $capabilities); + if (!$object) { + return new Aphront404Response(); + } + } else { + // Make sure the viewer has permission to create new objects of + // this type if we're going to create a new object. + if ($require_create) { + $this->requireCreateCapability(); + } - if ($id) { - $this->setIsCreate(false); - $object = $this->newObjectFromID($id, $capabilities); - if (!$object) { - return new Aphront404Response(); + $this->setIsCreate(true); + $object = $this->newEditableObject(); } } else { - // Make sure the viewer has permission to create new objects of - // this type if we're going to create a new object. - if ($require_create) { - $this->requireCreateCapability(); - } - - $this->setIsCreate(true); - $object = $this->newEditableObject(); + $id = $object->getID(); } $this->validateObject($object); @@ -831,7 +858,7 @@ $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 @@ $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)); diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -456,8 +456,14 @@ return null; } + $object = $this->getObject(); + + if (!($object instanceof PhabricatorCustomFieldInterface)) { + return null; + } + $field = PhabricatorCustomField::getObjectField( - $this->getObject(), + $object, PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS, $key); if (!$field) {