1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Allow Almanac properties to be deleted, use EditEngine instead of CustomField

Summary:
Fixes T10410. Immediate impact of this is that you can now actually delete properties from Almanac services, devices and bindings.

The meat of the change is switching from CustomField to EditEngine for most of the actual editing logic. CustomField creates a lot of problems with using EditEngine for everything else (D15326), and weird, hard-to-resolve bugs like this one (not being able to delete stuff).

Using EditEngine to do this stuff instead seems like it works out much better -- I did this in ProfilePanel first and am happy with how it looks.

This also makes the internal storage for properties JSON instead of raw text.

Test Plan:
  - Created, edited and deleted properties on services, devices and bindings.
  - Edited and reset builtin properties on repository services.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10410

Differential Revision: https://secure.phabricator.com/D15327
This commit is contained in:
epriestley 2016-02-22 06:35:30 -08:00
parent 411331469a
commit ab86523ac4
32 changed files with 605 additions and 498 deletions

View file

@ -0,0 +1,28 @@
<?php
$table = new AlmanacProperty();
$conn_w = $table->establishConnection('w');
// We're going to JSON-encode the value in each row: previously rows stored
// plain strings, but now they store JSON, so we need to update them.
foreach (new LiskMigrationIterator($table) as $property) {
$key = $property->getFieldName();
$current_row = queryfx_one(
$conn_w,
'SELECT fieldValue FROM %T WHERE id = %d',
$table->getTableName(),
$property->getID());
if (!$current_row) {
continue;
}
queryfx(
$conn_w,
'UPDATE %T SET fieldValue = %s WHERE id = %d',
$table->getTableName(),
phutil_json_encode($current_row['fieldValue']),
$property->getID());
}

View file

@ -14,6 +14,7 @@ phutil_register_library_map(array(
'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php',
'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php',
'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php',
'AlmanacBindingPropertyEditEngine' => 'applications/almanac/editor/AlmanacBindingPropertyEditEngine.php',
'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php',
'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php',
'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php',
@ -25,13 +26,11 @@ phutil_register_library_map(array(
'AlmanacConduitAPIMethod' => 'applications/almanac/conduit/AlmanacConduitAPIMethod.php',
'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php',
'AlmanacController' => 'applications/almanac/controller/AlmanacController.php',
'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php',
'AlmanacCreateClusterServicesCapability' => 'applications/almanac/capability/AlmanacCreateClusterServicesCapability.php',
'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php',
'AlmanacCreateNamespacesCapability' => 'applications/almanac/capability/AlmanacCreateNamespacesCapability.php',
'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php',
'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php',
'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php',
'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php',
'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php',
'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php',
@ -41,12 +40,14 @@ phutil_register_library_map(array(
'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php',
'AlmanacDeviceNameNgrams' => 'applications/almanac/storage/AlmanacDeviceNameNgrams.php',
'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php',
'AlmanacDevicePropertyEditEngine' => 'applications/almanac/editor/AlmanacDevicePropertyEditEngine.php',
'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php',
'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php',
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php',
'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php',
'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php',
'AlmanacEditor' => 'applications/almanac/editor/AlmanacEditor.php',
'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php',
'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php',
'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php',
@ -92,6 +93,7 @@ phutil_register_library_map(array(
'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php',
'AlmanacPropertyDeleteController' => 'applications/almanac/controller/AlmanacPropertyDeleteController.php',
'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php',
'AlmanacPropertyEditEngine' => 'applications/almanac/editor/AlmanacPropertyEditEngine.php',
'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php',
'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php',
'AlmanacQuery' => 'applications/almanac/query/AlmanacQuery.php',
@ -106,6 +108,7 @@ phutil_register_library_map(array(
'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php',
'AlmanacServiceNameNgrams' => 'applications/almanac/storage/AlmanacServiceNameNgrams.php',
'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php',
'AlmanacServicePropertyEditEngine' => 'applications/almanac/editor/AlmanacServicePropertyEditEngine.php',
'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php',
'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php',
'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php',
@ -113,6 +116,7 @@ phutil_register_library_map(array(
'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php',
'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php',
'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php',
'AlmanacTransaction' => 'applications/almanac/storage/AlmanacTransaction.php',
'AphlictDropdownDataQuery' => 'applications/aphlict/query/AphlictDropdownDataQuery.php',
'Aphront304Response' => 'aphront/response/Aphront304Response.php',
'Aphront400Response' => 'aphront/response/Aphront400Response.php',
@ -3989,17 +3993,17 @@ phutil_register_library_map(array(
'AlmanacBinding' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorApplicationTransactionInterface',
'AlmanacPropertyInterface',
'PhabricatorDestructibleInterface',
),
'AlmanacBindingEditController' => 'AlmanacServiceController',
'AlmanacBindingEditor' => 'PhabricatorApplicationTransactionEditor',
'AlmanacBindingEditor' => 'AlmanacEditor',
'AlmanacBindingPHIDType' => 'PhabricatorPHIDType',
'AlmanacBindingPropertyEditEngine' => 'AlmanacPropertyEditEngine',
'AlmanacBindingQuery' => 'AlmanacQuery',
'AlmanacBindingTableView' => 'AphrontView',
'AlmanacBindingTransaction' => 'PhabricatorApplicationTransaction',
'AlmanacBindingTransaction' => 'AlmanacTransaction',
'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacBindingViewController' => 'AlmanacServiceController',
'AlmanacClusterDatabaseServiceType' => 'AlmanacClusterServiceType',
@ -4008,22 +4012,16 @@ phutil_register_library_map(array(
'AlmanacConduitAPIMethod' => 'ConduitAPIMethod',
'AlmanacConsoleController' => 'AlmanacController',
'AlmanacController' => 'PhabricatorController',
'AlmanacCoreCustomField' => array(
'AlmanacCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateNamespacesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability',
'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability',
'AlmanacCustomField' => 'PhabricatorCustomField',
'AlmanacCustomServiceType' => 'AlmanacServiceType',
'AlmanacDAO' => 'PhabricatorLiskDAO',
'AlmanacDevice' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorProjectInterface',
'PhabricatorSSHPublicKeyInterface',
@ -4033,16 +4031,18 @@ phutil_register_library_map(array(
),
'AlmanacDeviceController' => 'AlmanacController',
'AlmanacDeviceEditController' => 'AlmanacDeviceController',
'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor',
'AlmanacDeviceEditor' => 'AlmanacEditor',
'AlmanacDeviceListController' => 'AlmanacDeviceController',
'AlmanacDeviceNameNgrams' => 'PhabricatorSearchNgrams',
'AlmanacDevicePHIDType' => 'PhabricatorPHIDType',
'AlmanacDevicePropertyEditEngine' => 'AlmanacPropertyEditEngine',
'AlmanacDeviceQuery' => 'AlmanacQuery',
'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction',
'AlmanacDeviceTransaction' => 'AlmanacTransaction',
'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacDeviceViewController' => 'AlmanacDeviceController',
'AlmanacDrydockPoolServiceType' => 'AlmanacServiceType',
'AlmanacEditor' => 'PhabricatorApplicationTransactionEditor',
'AlmanacInterface' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
@ -4065,7 +4065,6 @@ phutil_register_library_map(array(
'AlmanacNamespace' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorProjectInterface',
'AlmanacPropertyInterface',
@ -4104,12 +4103,13 @@ phutil_register_library_map(array(
'AlmanacNetworkViewController' => 'AlmanacNetworkController',
'AlmanacPropertiesDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
'AlmanacProperty' => array(
'PhabricatorCustomFieldStorage',
'AlmanacDAO',
'PhabricatorPolicyInterface',
),
'AlmanacPropertyController' => 'AlmanacController',
'AlmanacPropertyDeleteController' => 'AlmanacDeviceController',
'AlmanacPropertyEditController' => 'AlmanacDeviceController',
'AlmanacPropertyEditEngine' => 'PhabricatorEditEngine',
'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'AlmanacQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'AlmanacQueryDevicesConduitAPIMethod' => 'AlmanacConduitAPIMethod',
@ -4118,7 +4118,6 @@ phutil_register_library_map(array(
'AlmanacService' => array(
'AlmanacDAO',
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorApplicationTransactionInterface',
'PhabricatorProjectInterface',
'AlmanacPropertyInterface',
@ -4128,17 +4127,19 @@ phutil_register_library_map(array(
'AlmanacServiceController' => 'AlmanacController',
'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource',
'AlmanacServiceEditController' => 'AlmanacServiceController',
'AlmanacServiceEditor' => 'PhabricatorApplicationTransactionEditor',
'AlmanacServiceEditor' => 'AlmanacEditor',
'AlmanacServiceListController' => 'AlmanacServiceController',
'AlmanacServiceNameNgrams' => 'PhabricatorSearchNgrams',
'AlmanacServicePHIDType' => 'PhabricatorPHIDType',
'AlmanacServicePropertyEditEngine' => 'AlmanacPropertyEditEngine',
'AlmanacServiceQuery' => 'AlmanacQuery',
'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction',
'AlmanacServiceTransaction' => 'AlmanacTransaction',
'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacServiceType' => 'Phobject',
'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase',
'AlmanacServiceViewController' => 'AlmanacServiceController',
'AlmanacTransaction' => 'PhabricatorApplicationTransaction',
'AphlictDropdownDataQuery' => 'Phobject',
'Aphront304Response' => 'AphrontResponse',
'Aphront400Response' => 'AphrontResponse',

View file

@ -43,12 +43,12 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
return array(
'/almanac/' => array(
'' => 'AlmanacConsoleController',
'service/' => array(
'(?P<objectType>service)/' => array(
$this->getQueryRoutePattern() => 'AlmanacServiceListController',
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacServiceEditController',
'view/(?P<name>[^/]+)/' => 'AlmanacServiceViewController',
),
'device/' => array(
'(?P<objectType>device)/' => array(
$this->getQueryRoutePattern() => 'AlmanacDeviceListController',
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacDeviceEditController',
'view/(?P<name>[^/]+)/' => 'AlmanacDeviceViewController',
@ -65,16 +65,16 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacNetworkEditController',
'(?P<id>\d+)/' => 'AlmanacNetworkViewController',
),
'property/' => array(
'edit/' => 'AlmanacPropertyEditController',
'delete/' => 'AlmanacPropertyDeleteController',
),
'namespace/' => array(
$this->getQueryRoutePattern() => 'AlmanacNamespaceListController',
$this->getEditRoutePattern('edit/')
=> 'AlmanacNamespaceEditController',
'(?P<id>\d+)/' => 'AlmanacNamespaceViewController',
),
'property/' => array(
'delete/' => 'AlmanacPropertyDeleteController',
'update/' => 'AlmanacPropertyEditController',
),
),
);
}

View file

@ -10,27 +10,14 @@ abstract class AlmanacController
$properties = $object->getAlmanacProperties();
$this->requireResource('almanac-css');
Javelin::initBehavior('phabricator-tooltips', array());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$object,
PhabricatorPolicyCapability::CAN_EDIT);
$field_list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_DEFAULT);
// Before reading values from the object, read defaults.
$defaults = mpull(
$field_list->getFields(),
'getValueForStorage',
'getFieldKey');
$field_list
->setViewer($viewer)
->readFieldsFromStorage($object);
Javelin::initBehavior('phabricator-tooltips', array());
$properties = $object->getAlmanacProperties();
$icon_builtin = id(new PHUIIconView())
->setIcon('fa-circle')
@ -51,45 +38,46 @@ abstract class AlmanacController
));
$builtins = $object->getAlmanacPropertyFieldSpecifications();
$defaults = mpull($builtins, null, 'getValueForTransaction');
// Sort fields so builtin fields appear first, then fields are ordered
// alphabetically.
$fields = $field_list->getFields();
$fields = msort($fields, 'getFieldKey');
$properties = msort($properties, 'getFieldName');
$head = array();
$tail = array();
foreach ($fields as $field) {
$key = $field->getFieldKey();
foreach ($properties as $property) {
$key = $property->getFieldName();
if (isset($builtins[$key])) {
$head[$key] = $field;
$head[$key] = $property;
} else {
$tail[$key] = $field;
$tail[$key] = $property;
}
}
$fields = $head + $tail;
$properties = $head + $tail;
$delete_base = $this->getApplicationURI('property/delete/');
$edit_base = $this->getApplicationURI('property/update/');
$rows = array();
foreach ($fields as $key => $field) {
$value = $field->getValueForStorage();
foreach ($properties as $key => $property) {
$value = $property->getFieldValue();
$is_builtin = isset($builtins[$key]);
$delete_uri = $this->getApplicationURI('property/delete/');
$delete_uri = id(new PhutilURI($delete_uri))
$delete_uri = id(new PhutilURI($delete_base))
->setQueryParams(
array(
'objectPHID' => $object->getPHID(),
'key' => $key,
'objectPHID' => $object->getPHID(),
));
$edit_uri = $this->getApplicationURI('property/edit/');
$edit_uri = id(new PhutilURI($edit_uri))
$edit_uri = id(new PhutilURI($edit_base))
->setQueryParams(
array(
'objectPHID' => $object->getPHID(),
'key' => $key,
'objectPHID' => $object->getPHID(),
));
$delete = javelin_tag(
@ -153,7 +141,8 @@ abstract class AlmanacController
));
$phid = $object->getPHID();
$add_uri = $this->getApplicationURI("property/edit/?objectPHID={$phid}");
$add_uri = id(new PhutilURI($edit_base))
->setQueryParam('objectPHID', $object->getPHID());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
@ -196,4 +185,12 @@ abstract class AlmanacController
$box->setInfoView($error_view);
}
protected function getPropertyDeleteURI($object) {
return null;
}
protected function getPropertyUpdateURI($object) {
return null;
}
}

View file

@ -34,53 +34,24 @@ final class AlmanacPropertyDeleteController
$is_builtin = isset($builtins[$key]);
if ($is_builtin) {
// This is a builtin property, so we're going to reset it to the
// default value.
$field_list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_DEFAULT);
// Note that we're NOT loading field values from the object: we just want
// to get the field's default value so we can reset it.
$fields = $field_list->getFields();
$field = $fields[$key];
$is_delete = false;
$new_value = $field->getValueForStorage();
// Now, load the field to get the old value.
$field_list
->setViewer($viewer)
->readFieldsFromStorage($object);
$old_value = $field->getValueForStorage();
$title = pht('Reset Property');
$body = pht('Reset this property to its default value?');
$submit_text = pht('Reset');
$body = pht(
'Reset property "%s" to its default value?',
$key);
$submit_text = pht('Reset Property');
} else {
// This is a custom property, so we're going to delete it outright.
$is_delete = true;
$old_value = $object->getAlmanacPropertyValue($key);
$new_value = null;
$title = pht('Delete Property');
$body = pht('Delete this property? TODO: DOES NOT WORK YET');
$submit_text = pht('Delete');
$body = pht(
'Delete property "%s"?',
$key);
$submit_text = pht('Delete Property');
}
$validation_exception = null;
if ($request->isFormPost()) {
$xaction = $object->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD)
->setMetadataValue('customfield:key', $key)
->setOldValue($old_value)
->setNewValue($new_value);
// TODO: We aren't really deleting properties that we claim to delete
// yet, but that needs to be specialized a little bit.
->setTransactionType(AlmanacTransaction::TYPE_PROPERTY_REMOVE)
->setMetadataValue('almanac.property', $key);
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)

View file

@ -24,133 +24,81 @@ final class AlmanacPropertyEditController
}
$cancel_uri = $object->getURI();
$property_key = $request->getStr('key');
$key = $request->getStr('key');
if ($key) {
$property_key = $key;
$is_new = false;
$title = pht('Edit Property');
$save_button = pht('Save Changes');
if (!strlen($property_key)) {
return $this->buildPropertyKeyResponse($cancel_uri, null);
} else {
$property_key = null;
$is_new = true;
$title = pht('Add Property');
$save_button = pht('Add Property');
}
if ($is_new) {
$errors = array();
$property = null;
$v_name = null;
$e_name = true;
if ($request->isFormPost()) {
$name = $request->getStr('name');
if (!strlen($name)) {
$e_name = pht('Required');
$errors[] = pht('You must provide a property name.');
} else {
$caught = null;
try {
AlmanacNames::validateName($name);
} catch (Exception $ex) {
$caught = $ex;
}
if ($caught) {
$e_name = pht('Invalid');
$errors[] = $caught->getMessage();
}
}
if (!$errors) {
$property_key = $name;
}
}
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);
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$error = null;
try {
$editor->applyTransactions($object, $xactions);
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
AlmanacNames::validateName($property_key);
} catch (Exception $ex) {
$error = $ex->getMessage();
}
// NOTE: If you enter an existing name, we're just treating it as an
// edit operation. This might be a little confusing.
if ($error !== null) {
if ($request->isFormPost()) {
// The user is creating a new property and picked a bad name. Give
// them an opportunity to fix it.
return $this->buildPropertyKeyResponse($cancel_uri, $error);
} else {
// The user is editing an invalid property.
return $this->newDialog()
->setTitle(pht('Invalid Property'))
->appendParagraph(
pht(
'The property name "%s" is invalid. This property can not '.
'be edited.',
$property_key))
->appendParagraph($error)
->addCancelButton($cancel_uri);
}
}
}
return $object->newAlmanacPropertyEditEngine()
->addContextParameter('objectPHID')
->addContextParameter('key')
->setTargetObject($object)
->setPropertyKey($property_key)
->setController($this)
->buildResponse();
}
private function buildPropertyKeyResponse($cancel_uri, $error) {
$viewer = $this->getViewer();
$request = $this->getRequest();
$v_key = $request->getStr('key');
if ($error !== null) {
$e_key = pht('Invalid');
} else {
$e_key = true;
}
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('objectPHID', $request->getStr('objectPHID'))
->addHiddenInput('key', $request->getStr('key'))
->addHiddenInput('name', $property_key)
->addHiddenInput('isValueEdit', true);
->appendChild(
id(new AphrontFormTextControl())
->setName('key')
->setLabel(pht('Name'))
->setValue($v_key)
->setError($e_key));
$field_list->appendFieldsToForm($form);
$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);
}

View file

@ -11,4 +11,14 @@ abstract class AlmanacServiceController extends AlmanacController {
return $crumbs;
}
protected function getPropertyDeleteURI($object) {
$id = $object->getID();
return "/almanac/service/delete/{$id}/";
}
protected function getPropertyUpdateURI($object) {
$id = $object->getID();
return "/almanac/service/property/{$id}/";
}
}

View file

@ -1,80 +0,0 @@
<?php
final class AlmanacCoreCustomField
extends AlmanacCustomField
implements PhabricatorStandardCustomFieldInterface {
public function getStandardCustomFieldNamespace() {
return 'almanac:core';
}
public function getFieldKey() {
return $this->getProxy()->getRawStandardFieldKey();
}
public function getFieldName() {
return $this->getFieldKey();
}
public function createFields($object) {
if (!$object->getID()) {
return array();
}
$specs = $object->getAlmanacPropertyFieldSpecifications();
$default_specs = array();
foreach ($object->getAlmanacProperties() as $property) {
$default_specs[$property->getFieldName()] = array(
'name' => $property->getFieldName(),
'type' => 'text',
);
}
return PhabricatorStandardCustomField::buildStandardFields(
$this,
$specs + $default_specs);
}
public function shouldUseStorage() {
return false;
}
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
$key = $this->getFieldKey();
if ($object->hasAlmanacProperty($key)) {
$this->setValueFromStorage($object->getAlmanacPropertyValue($key));
}
}
public function applyApplicationTransactionInternalEffects(
PhabricatorApplicationTransaction $xaction) {
return;
}
public function applyApplicationTransactionExternalEffects(
PhabricatorApplicationTransaction $xaction) {
$object = $this->getObject();
$phid = $object->getPHID();
$key = $this->getFieldKey();
$property = id(new AlmanacPropertyQuery())
->setViewer($this->getViewer())
->withObjectPHIDs(array($phid))
->withNames(array($key))
->executeOne();
if (!$property) {
$property = id(new AlmanacProperty())
->setObjectPHID($phid)
->setFieldIndex(PhabricatorHash::digestForIndex($key))
->setFieldName($key);
}
$property
->setFieldValue($xaction->getNewValue())
->save();
}
}

View file

@ -1,4 +0,0 @@
<?php
abstract class AlmanacCustomField
extends PhabricatorCustomField {}

View file

@ -1,11 +1,7 @@
<?php
final class AlmanacBindingEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
extends AlmanacEditor {
public function getEditorObjectsDescription() {
return pht('Almanac Binding');

View file

@ -0,0 +1,16 @@
<?php
final class AlmanacBindingPropertyEditEngine
extends AlmanacPropertyEditEngine {
const ENGINECONST = 'almanac.binding.property';
protected function newObjectQuery() {
return new AlmanacBindingQuery();
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
}

View file

@ -1,20 +1,12 @@
<?php
final class AlmanacDeviceEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
extends AlmanacEditor {
public function getEditorObjectsDescription() {
return pht('Almanac Device');
}
protected function supportsSearch() {
return true;
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();

View file

@ -0,0 +1,16 @@
<?php
final class AlmanacDevicePropertyEditEngine
extends AlmanacPropertyEditEngine {
const ENGINECONST = 'almanac.device.property';
protected function newObjectQuery() {
return new AlmanacDeviceQuery();
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
}

View file

@ -0,0 +1,156 @@
<?php
abstract class AlmanacEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
protected function supportsSearch() {
return true;
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = AlmanacTransaction::TYPE_PROPERTY_UPDATE;
$types[] = AlmanacTransaction::TYPE_PROPERTY_REMOVE;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
$property_key = $xaction->getMetadataValue('almanac.property');
$exists = $object->hasAlmanacProperty($property_key);
$value = $object->getAlmanacPropertyValue($property_key);
return array(
'existed' => $exists,
'value' => $value,
);
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
$property_key = $xaction->getMetadataValue('almanac.property');
if ($object->hasAlmanacProperty($property_key)) {
$property = $object->getAlmanacProperty($property_key);
} else {
$property = id(new AlmanacProperty())
->setObjectPHID($object->getPHID())
->setFieldName($property_key);
}
$property
->setFieldValue($xaction->getNewValue())
->save();
return;
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
$property_key = $xaction->getMetadataValue('almanac.property');
if ($object->hasAlmanacProperty($property_key)) {
$property = $object->getAlmanacProperty($property_key);
$property->delete();
}
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case AlmanacTransaction::TYPE_PROPERTY_UPDATE:
foreach ($xactions as $xaction) {
$property_key = $xaction->getMetadataValue('almanac.property');
$message = null;
try {
AlmanacNames::validateName($property_key);
} catch (Exception $ex) {
$message = $ex->getMessage();
}
if ($message !== null) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
$message,
$xaction);
$errors[] = $error;
continue;
}
$new_value = $xaction->getNewValue();
try {
phutil_json_encode($new_value);
} catch (Exception $ex) {
$message = pht(
'Almanac property values must be representable in JSON. %s',
$ex->getMessage());
}
if ($message !== null) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
$message,
$xaction);
$errors[] = $error;
continue;
}
}
break;
case AlmanacTransaction::TYPE_PROPERTY_REMOVE:
// NOTE: No name validation on removals since it's OK to delete
// an invalid property that somehow came into existence.
break;
}
return $errors;
}
}

View file

@ -0,0 +1,79 @@
<?php
abstract class AlmanacPropertyEditEngine
extends PhabricatorEditEngine {
private $propertyKey;
public function setPropertyKey($property_key) {
$this->propertyKey = $property_key;
return $this;
}
public function getPropertyKey() {
return $this->propertyKey;
}
public function isEngineConfigurable() {
return false;
}
public function isEngineExtensible() {
return false;
}
public function getEngineName() {
return pht('Almanac Properties');
}
public function getSummaryHeader() {
return pht('Edit Almanac Property Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit Almanac properties.');
}
public function getEngineApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
protected function newEditableObject() {
throw new PhutilMethodNotImplementedException();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Property');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Property');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Property: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Property');
}
protected function getObjectCreateShortText() {
return pht('Create Property');
}
protected function buildCustomEditFields($object) {
$property_key = $this->getPropertyKey();
$xaction_type = AlmanacTransaction::TYPE_PROPERTY_UPDATE;
return array(
id(new PhabricatorTextEditField())
->setKey('value')
->setMetadataValue('almanac.property', $property_key)
->setLabel($property_key)
->setTransactionType($xaction_type)
->setValue($object->getAlmanacPropertyValue($property_key)),
);
}
}

View file

@ -1,20 +1,12 @@
<?php
final class AlmanacServiceEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
extends AlmanacEditor {
public function getEditorObjectsDescription() {
return pht('Almanac Service');
}
protected function supportsSearch() {
return true;
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
@ -185,6 +177,4 @@ final class AlmanacServiceEditor
return $errors;
}
}

View file

@ -0,0 +1,16 @@
<?php
final class AlmanacServicePropertyEditEngine
extends AlmanacPropertyEditEngine {
const ENGINECONST = 'almanac.service.property';
protected function newObjectQuery() {
return new AlmanacServiceQuery();
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
}

View file

@ -8,5 +8,6 @@ interface AlmanacPropertyInterface {
public function getAlmanacProperty($key);
public function getAlmanacPropertyValue($key, $default = null);
public function getAlmanacPropertyFieldSpecifications();
public function newAlmanacPropertyEditEngine();
}

View file

@ -5,9 +5,8 @@ abstract class AlmanacQuery
protected function didFilterPage(array $objects) {
if (head($objects) instanceof AlmanacPropertyInterface) {
// NOTE: We load properties unconditionally because CustomField assumes
// it can always generate a list of fields on an object. It may make
// sense to re-examine that assumption eventually.
// NOTE: We load properties for obsolete historical reasons. It may make
// sense to re-examine that assumption shortly.
$property_query = id(new AlmanacPropertyQuery())
->setViewer($this->getViewer())
@ -25,9 +24,23 @@ abstract class AlmanacQuery
$properties = mgroup($properties, 'getObjectPHID');
foreach ($objects as $object) {
$object_properties = idx($properties, $object->getPHID(), array());
$object_properties = mpull($object_properties, null, 'getFieldName');
// Create synthetic properties for defaults on the object itself.
$specs = $object->getAlmanacPropertyFieldSpecifications();
foreach ($specs as $key => $spec) {
if (empty($object_properties[$key])) {
$object_properties[$key] = id(new AlmanacProperty())
->setObjectPHID($object->getPHID())
->setFieldName($key)
->setFieldValue($spec->getValueForTransaction());
}
}
foreach ($object_properties as $property) {
$property->attachObject($object);
}
$object->attachAlmanacProperties($object_properties);
}
}

View file

@ -16,8 +16,4 @@ final class AlmanacClusterDatabaseServiceType
'Defines a database service for use in a Phabricator cluster.');
}
public function getFieldSpecifications() {
return array();
}
}

View file

@ -18,16 +18,7 @@ final class AlmanacClusterRepositoryServiceType
public function getFieldSpecifications() {
return array(
'closed' => array(
'type' => 'bool',
'name' => pht('Closed'),
'default' => false,
'strings' => array(
'edit.checkbox' => pht(
'Prevent new repositories from being allocated on this '.
'service.'),
),
),
'closed' => id(new PhabricatorTextEditField()),
);
}

View file

@ -4,7 +4,6 @@ final class AlmanacBinding
extends AlmanacDAO
implements
PhabricatorPolicyInterface,
PhabricatorCustomFieldInterface,
PhabricatorApplicationTransactionInterface,
AlmanacPropertyInterface,
PhabricatorDestructibleInterface {
@ -17,7 +16,6 @@ final class AlmanacBinding
private $service = self::ATTACHABLE;
private $device = self::ATTACHABLE;
private $interface = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
private $almanacProperties = self::ATTACHABLE;
public static function initializeNewBinding(AlmanacService $service) {
@ -58,6 +56,10 @@ final class AlmanacBinding
return parent::save();
}
public function getName() {
return pht('Binding %s', $this->getID());
}
public function getURI() {
return '/almanac/binding/'.$this->getID().'/';
}
@ -124,6 +126,10 @@ final class AlmanacBinding
return array();
}
public function newAlmanacPropertyEditEngine() {
return new AlmanacBindingPropertyEditEngine();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -162,27 +168,6 @@ final class AlmanacBinding
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return array();
}
public function getCustomFieldBaseClass() {
return 'AlmanacCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */

View file

@ -1,7 +1,7 @@
<?php
final class AlmanacBindingTransaction
extends PhabricatorApplicationTransaction {
extends AlmanacTransaction {
const TYPE_INTERFACE = 'almanac:binding:interface';

View file

@ -4,7 +4,6 @@ final class AlmanacDevice
extends AlmanacDAO
implements
PhabricatorPolicyInterface,
PhabricatorCustomFieldInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorProjectInterface,
PhabricatorSSHPublicKeyInterface,
@ -19,7 +18,6 @@ final class AlmanacDevice
protected $editPolicy;
protected $isLocked;
private $customFields = self::ATTACHABLE;
private $almanacProperties = self::ATTACHABLE;
public static function initializeNewDevice() {
@ -137,6 +135,10 @@ final class AlmanacDevice
return array();
}
public function newAlmanacPropertyEditEngine() {
return new AlmanacDevicePropertyEditEngine();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -178,27 +180,6 @@ final class AlmanacDevice
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return array();
}
public function getCustomFieldBaseClass() {
return 'AlmanacCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */

View file

@ -1,7 +1,7 @@
<?php
final class AlmanacDeviceTransaction
extends PhabricatorApplicationTransaction {
extends AlmanacTransaction {
const TYPE_NAME = 'almanac:device:name';
const TYPE_INTERFACE = 'almanac:device:interface';

View file

@ -4,7 +4,6 @@ final class AlmanacNamespace
extends AlmanacDAO
implements
PhabricatorPolicyInterface,
PhabricatorCustomFieldInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorProjectInterface,
AlmanacPropertyInterface,
@ -17,7 +16,6 @@ final class AlmanacNamespace
protected $viewPolicy;
protected $editPolicy;
private $customFields = self::ATTACHABLE;
private $almanacProperties = self::ATTACHABLE;
public static function initializeNewNamespace() {
@ -148,6 +146,10 @@ final class AlmanacNamespace
return array();
}
public function newAlmanacPropertyEditEngine() {
throw new PhutilMethodNotImplementedException();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -177,27 +179,6 @@ final class AlmanacNamespace
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return array();
}
public function getCustomFieldBaseClass() {
return 'AlmanacCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */

View file

@ -1,25 +1,33 @@
<?php
final class AlmanacProperty
extends PhabricatorCustomFieldStorage
extends AlmanacDAO
implements PhabricatorPolicyInterface {
protected $objectPHID;
protected $fieldIndex;
protected $fieldName;
protected $fieldValue;
private $object = self::ATTACHABLE;
public function getApplicationName() {
return 'almanac';
}
protected function getConfiguration() {
$config = parent::getConfiguration();
$config[self::CONFIG_COLUMN_SCHEMA] += array(
'fieldName' => '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 @@ final class AlmanacProperty
return $this;
}
public static function buildTransactions(
AlmanacPropertyInterface $object,
array $properties) {
public function save() {
$hash = PhabricatorHash::digestForIndex($this->getFieldName());
$this->setFieldIndex($hash);
$template = $object->getApplicationTransactionTemplate();
$attached_properties = $object->getAlmanacProperties();
foreach ($properties as $key => $value) {
if (empty($attached_properties[$key])) {
$attached_properties[] = id(new AlmanacProperty())
->setObjectPHID($object->getPHID())
->setFieldName($key);
}
}
$object->attachAlmanacProperties($attached_properties);
$field_list = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_DEFAULT);
$fields = $field_list->getFields();
$xactions = array();
foreach ($properties as $name => $property) {
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_CUSTOMFIELD)
->setMetadataValue('customfield:key', $name)
->setOldValue($object->getAlmanacPropertyValue($name))
->setNewValue($property);
}
return $xactions;
return parent::save();
}

View file

@ -4,7 +4,6 @@ final class AlmanacService
extends AlmanacDAO
implements
PhabricatorPolicyInterface,
PhabricatorCustomFieldInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorProjectInterface,
AlmanacPropertyInterface,
@ -19,7 +18,6 @@ final class AlmanacService
protected $serviceClass;
protected $isLocked;
private $customFields = self::ATTACHABLE;
private $almanacProperties = self::ATTACHABLE;
private $bindings = self::ATTACHABLE;
private $serviceType = self::ATTACHABLE;
@ -130,6 +128,10 @@ final class AlmanacService
return $this->getServiceType()->getFieldSpecifications();
}
public function newAlmanacPropertyEditEngine() {
return new AlmanacServicePropertyEditEngine();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -171,27 +173,6 @@ final class AlmanacService
}
/* -( PhabricatorCustomFieldInterface )------------------------------------ */
public function getCustomFieldSpecificationForRole($role) {
return array();
}
public function getCustomFieldBaseClass() {
return 'AlmanacCustomField';
}
public function getCustomFields() {
return $this->assertAttached($this->customFields);
}
public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
$this->customFields = $fields;
return $this;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */

View file

@ -1,23 +1,15 @@
<?php
final class AlmanacServiceTransaction
extends PhabricatorApplicationTransaction {
extends AlmanacTransaction {
const TYPE_NAME = 'almanac:service:name';
const TYPE_LOCK = 'almanac:service:lock';
public function getApplicationName() {
return 'almanac';
}
public function getApplicationTransactionType() {
return AlmanacServicePHIDType::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();

View file

@ -0,0 +1,38 @@
<?php
abstract class AlmanacTransaction
extends PhabricatorApplicationTransaction {
const TYPE_PROPERTY_UPDATE = 'almanac:property:update';
const TYPE_PROPERTY_REMOVE = 'almanac:property:remove';
public function getApplicationName() {
return 'almanac';
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
switch ($this->getTransactionType()) {
case self::TYPE_PROPERTY_UPDATE:
$property_key = $this->getMetadataValue('almanac.property');
return pht(
'%s updated the property "%s".',
$this->renderHandleLink($author_phid),
$property_key);
case self::TYPE_PROPERTY_REMOVE:
$property_key = $this->getMetadataValue('almanac.property');
return pht(
'%s deleted the property "%s".',
$this->renderHandleLink($author_phid),
$property_key);
}
return parent::getTitle();
}
}

View file

@ -23,6 +23,7 @@ abstract class PhabricatorEditEngine
private $isCreate;
private $editEngineConfiguration;
private $contextParameters = array();
private $targetObject;
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@ -61,6 +62,22 @@ abstract class PhabricatorEditEngine
return true;
}
public function isEngineExtensible() {
return true;
}
/**
* Force the engine to edit a particular object.
*/
public function setTargetObject($target_object) {
$this->targetObject = $target_object;
return $this;
}
public function getTargetObject() {
return $this->targetObject;
}
/* -( Managing Fields )---------------------------------------------------- */
@ -94,7 +111,12 @@ abstract class PhabricatorEditEngine
$fields = mpull($fields, null, 'getKey');
$extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions();
if ($this->isEngineExtensible()) {
$extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions();
} else {
$extensions = array();
}
foreach ($extensions as $extension) {
$extension->setViewer($viewer);
@ -720,23 +742,28 @@ abstract class PhabricatorEditEngine
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();
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();
}
$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 @@ abstract class PhabricatorEditEngine
$template = $object->getApplicationTransactionTemplate();
$validation_exception = null;
if ($request->isFormPost()) {
if ($request->isFormPost() && $request->getBool('editEngine')) {
$submit_fields = $fields;
foreach ($submit_fields as $key => $field) {
@ -1044,7 +1071,8 @@ abstract class PhabricatorEditEngine
$request = $controller->getRequest();
$form = id(new AphrontFormView())
->setUser($viewer);
->setUser($viewer)
->addHiddenInput('editEngine', 'true');
foreach ($this->contextParameters as $param) {
$form->addHiddenInput($param, $request->getStr($param));

View file

@ -456,8 +456,14 @@ abstract class PhabricatorApplicationTransaction
return null;
}
$object = $this->getObject();
if (!($object instanceof PhabricatorCustomFieldInterface)) {
return null;
}
$field = PhabricatorCustomField::getObjectField(
$this->getObject(),
$object,
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
$key);
if (!$field) {