1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-09-20 01:08:50 +02:00

Implement a basic version of ApplicationEditor in Paste

Summary:
Ref T9132. Ref T4768. This is a rough v0 of ApplicationEditor, which replaces the edit workflow in Paste.

This mostly looks and works like ApplicationSearch, and is heavily modeled on it.

Roughly, we define a set of editable fields and the ApplicationEditor stuff builds everything else.

This has no functional changes, except:

  - I removed "Fork Paste" since I don't think it's particularly useful now that pastes are editable. We could restore it if users miss it.
  - Subscribers are now editable.
  - Form field order is a little goofy (this will be fixed in a future diff).
  - Subscribers and projects are now race-resistant.

The race-resistance works like this: instead of submitting just the new value ("subscribers=apple, dog") and doing a set operation ("set subscribers = apple, dog"), we submit the old and new values ("original=apple" + "new=apple, dog") then apply the user's changes as an add + remove ("add=dog", "remove=<none>"). This means that two users who do "Edit Paste" at around the same time and each add or remove a couple of subscribers won't overwrite each other, unless they actually add or remove the exact same subscribers (in which case their edits legitimately conflict). Previously, the last user to save would win, and whatever was in their field would overwrite the prior state, potentially losing the first user's edits.

Test Plan:
  - Created pastes.
  - Created pastes via API.
  - Edited pastes.
  - Edited every field.
  - Opened a paste in two windows and did project/subscriber edits in each, saved in arbitrary order, had edits respected.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4768, T9132

Differential Revision: https://secure.phabricator.com/D14390
This commit is contained in:
epriestley 2015-11-02 18:58:32 -08:00
parent 1b00ef08a0
commit 105cbaaee1
21 changed files with 978 additions and 270 deletions

View file

@ -1570,6 +1570,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php',
'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php',
'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php',
'PhabricatorApplicationEditEngine' => 'applications/transactions/editengine/PhabricatorApplicationEditEngine.php',
'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php',
'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php',
'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php',
@ -2063,6 +2064,7 @@ phutil_register_library_map(array(
'PhabricatorDataCacheSpec' => 'applications/cache/spec/PhabricatorDataCacheSpec.php',
'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php',
'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php',
'PhabricatorDatasourceEditField' => 'applications/transactions/editfield/PhabricatorDatasourceEditField.php',
'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php',
'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php',
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
@ -2097,6 +2099,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php',
'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php',
'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php',
'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php',
'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php',
'PhabricatorElasticSearchEngine' => 'applications/search/engine/PhabricatorElasticSearchEngine.php',
'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php',
@ -2554,6 +2557,7 @@ phutil_register_library_map(array(
'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php',
'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php',
'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php',
'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php',
'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php',
'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php',
'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php',
@ -2654,6 +2658,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyDAO' => 'applications/policy/storage/PhabricatorPolicyDAO.php',
'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php',
'PhabricatorPolicyEditController' => 'applications/policy/controller/PhabricatorPolicyEditController.php',
'PhabricatorPolicyEditField' => 'applications/transactions/editfield/PhabricatorPolicyEditField.php',
'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php',
'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php',
'PhabricatorPolicyFilter' => 'applications/policy/filter/PhabricatorPolicyFilter.php',
@ -2917,6 +2922,7 @@ phutil_register_library_map(array(
'PhabricatorSearchWorker' => 'applications/search/worker/PhabricatorSearchWorker.php',
'PhabricatorSecurityConfigOptions' => 'applications/config/option/PhabricatorSecurityConfigOptions.php',
'PhabricatorSecuritySetupCheck' => 'applications/config/check/PhabricatorSecuritySetupCheck.php',
'PhabricatorSelectEditField' => 'applications/transactions/editfield/PhabricatorSelectEditField.php',
'PhabricatorSendGridConfigOptions' => 'applications/config/option/PhabricatorSendGridConfigOptions.php',
'PhabricatorSessionsSettingsPanel' => 'applications/settings/panel/PhabricatorSessionsSettingsPanel.php',
'PhabricatorSettingsAddEmailAction' => 'applications/settings/action/PhabricatorSettingsAddEmailAction.php',
@ -2957,6 +2963,7 @@ phutil_register_library_map(array(
'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php',
'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php',
'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php',
'PhabricatorSpaceEditField' => 'applications/transactions/editfield/PhabricatorSpaceEditField.php',
'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php',
'PhabricatorSpacesArchiveController' => 'applications/spaces/controller/PhabricatorSpacesArchiveController.php',
'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php',
@ -3063,6 +3070,8 @@ phutil_register_library_map(array(
'PhabricatorTestNoCycleEdgeType' => 'applications/transactions/edges/PhabricatorTestNoCycleEdgeType.php',
'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php',
'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php',
'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php',
'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php',
'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php',
'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php',
'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php',
@ -3084,6 +3093,7 @@ phutil_register_library_map(array(
'PhabricatorTokenReceiverQuery' => 'applications/tokens/query/PhabricatorTokenReceiverQuery.php',
'PhabricatorTokenTokenPHIDType' => 'applications/tokens/phid/PhabricatorTokenTokenPHIDType.php',
'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php',
'PhabricatorTokenizerEditField' => 'applications/transactions/editfield/PhabricatorTokenizerEditField.php',
'PhabricatorTokensApplication' => 'applications/tokens/application/PhabricatorTokensApplication.php',
'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php',
'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php',
@ -5489,6 +5499,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationEditEngine' => 'Phobject',
'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController',
'PhabricatorApplicationLaunchView' => 'AphrontTagView',
'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController',
@ -6080,6 +6091,7 @@ phutil_register_library_map(array(
'PhabricatorDataCacheSpec' => 'PhabricatorCacheSpec',
'PhabricatorDataNotAttachedException' => 'Exception',
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDatasourceEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDebugController' => 'PhabricatorController',
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
@ -6113,6 +6125,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeTestCase' => 'PhabricatorTestCase',
'PhabricatorEdgeType' => 'Phobject',
'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase',
'PhabricatorEditField' => 'Phobject',
'PhabricatorEditor' => 'Phobject',
'PhabricatorElasticSearchEngine' => 'PhabricatorSearchEngine',
'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck',
@ -6647,6 +6660,7 @@ phutil_register_library_map(array(
'PhabricatorPasteController' => 'PhabricatorController',
'PhabricatorPasteDAO' => 'PhabricatorLiskDAO',
'PhabricatorPasteEditController' => 'PhabricatorPasteController',
'PhabricatorPasteEditEngine' => 'PhabricatorApplicationEditEngine',
'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorPasteListController' => 'PhabricatorPasteController',
'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType',
@ -6762,6 +6776,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyDAO' => 'PhabricatorLiskDAO',
'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyEditController' => 'PhabricatorPolicyController',
'PhabricatorPolicyEditField' => 'PhabricatorEditField',
'PhabricatorPolicyException' => 'Exception',
'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController',
'PhabricatorPolicyFilter' => 'Phobject',
@ -7090,6 +7105,7 @@ phutil_register_library_map(array(
'PhabricatorSearchWorker' => 'PhabricatorWorker',
'PhabricatorSecurityConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorSecuritySetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorSelectEditField' => 'PhabricatorEditField',
'PhabricatorSendGridConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsAddEmailAction' => 'PhabricatorSystemAction',
@ -7140,6 +7156,7 @@ phutil_register_library_map(array(
'PhabricatorSlugTestCase' => 'PhabricatorTestCase',
'PhabricatorSortTableUIExample' => 'PhabricatorUIExample',
'PhabricatorSourceCodeView' => 'AphrontView',
'PhabricatorSpaceEditField' => 'PhabricatorEditField',
'PhabricatorSpacesApplication' => 'PhabricatorApplication',
'PhabricatorSpacesArchiveController' => 'PhabricatorSpacesController',
'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability',
@ -7252,6 +7269,8 @@ phutil_register_library_map(array(
'PhabricatorTestNoCycleEdgeType' => 'PhabricatorEdgeType',
'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorTestWorker' => 'PhabricatorWorker',
'PhabricatorTextAreaEditField' => 'PhabricatorEditField',
'PhabricatorTextEditField' => 'PhabricatorEditField',
'PhabricatorTime' => 'Phobject',
'PhabricatorTimeGuard' => 'Phobject',
'PhabricatorTimeTestCase' => 'PhabricatorTestCase',
@ -7278,6 +7297,7 @@ phutil_register_library_map(array(
'PhabricatorTokenReceiverQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorTokenTokenPHIDType' => 'PhabricatorPHIDType',
'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener',
'PhabricatorTokenizerEditField' => 'PhabricatorEditField',
'PhabricatorTokensApplication' => 'PhabricatorApplication',
'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorTooltipUIExample' => 'PhabricatorUIExample',

View file

@ -521,6 +521,14 @@ abstract class PhabricatorController extends AphrontController {
}
public function buildApplicationCrumbsForEditEngine() {
// TODO: This is kind of gross, I'm bascially just making this public so
// I can use it in EditEngine. We could do this without making it public
// by using controller delegation, or make it properly public.
return $this->buildApplicationCrumbs();
}
/* -( Deprecated )--------------------------------------------------------- */

View file

@ -44,16 +44,11 @@ final class PasteCreateConduitAPIMethod extends PasteConduitAPIMethod {
$paste = PhabricatorPaste::initializeNewPaste($viewer);
$file = PhabricatorPasteEditor::initializeFileForPaste(
$viewer,
$title,
$content);
$xactions = array();
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
->setNewValue($file->getPHID());
->setNewValue($content);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)

View file

@ -3,248 +3,9 @@
final class PhabricatorPasteEditController extends PhabricatorPasteController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$parent = null;
$parent_id = null;
if (!$id) {
$is_create = true;
$paste = PhabricatorPaste::initializeNewPaste($viewer);
$parent_id = $request->getStr('parent');
if ($parent_id) {
// NOTE: If the Paste is forked from a paste which the user no longer
// has permission to see, we still let them edit it.
$parent = id(new PhabricatorPasteQuery())
->setViewer($viewer)
->withIDs(array($parent_id))
->needContent(true)
->needRawContent(true)
->execute();
$parent = head($parent);
if ($parent) {
$paste->setParentPHID($parent->getPHID());
$paste->setViewPolicy($parent->getViewPolicy());
}
}
$paste->setAuthorPHID($viewer->getPHID());
$paste->attachRawContent('');
} else {
$is_create = false;
$paste = id(new PhabricatorPasteQuery())
->setViewer($viewer)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->withIDs(array($id))
->needRawContent(true)
->executeOne();
if (!$paste) {
return new Aphront404Response();
}
}
$v_space = $paste->getSpacePHID();
if ($is_create && $parent) {
$v_title = pht('Fork of %s', $parent->getFullName());
$v_language = $parent->getLanguage();
$v_text = $parent->getRawContent();
$v_space = $parent->getSpacePHID();
} else {
$v_title = $paste->getTitle();
$v_language = $paste->getLanguage();
$v_text = $paste->getRawContent();
}
$v_view_policy = $paste->getViewPolicy();
$v_edit_policy = $paste->getEditPolicy();
$v_status = $paste->getStatus();
if ($is_create) {
$v_projects = array();
} else {
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
$paste->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
$v_projects = array_reverse($v_projects);
}
$validation_exception = null;
if ($request->isFormPost()) {
$xactions = array();
$v_text = $request->getStr('text');
$v_title = $request->getStr('title');
$v_language = $request->getStr('language');
$v_view_policy = $request->getStr('can_view');
$v_edit_policy = $request->getStr('can_edit');
$v_projects = $request->getArr('projects');
$v_space = $request->getStr('spacePHID');
$v_status = $request->getStr('status');
// NOTE: The author is the only editor and can always view the paste,
// so it's impossible for them to choose an invalid policy.
if ($is_create || ($v_text !== $paste->getRawContent())) {
$file = PhabricatorPasteEditor::initializeFileForPaste(
$viewer,
$v_title,
$v_text);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
->setNewValue($file->getPHID());
}
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
->setNewValue($v_title);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
->setNewValue($v_language);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($v_view_policy);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($v_edit_policy);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_SPACE)
->setNewValue($v_space);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS)
->setNewValue($v_status);
$proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $proj_edge_type)
->setNewValue(array('=' => array_fuse($v_projects)));
$editor = id(new PhabricatorPasteEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$xactions = $editor->applyTransactions($paste, $xactions);
return id(new AphrontRedirectResponse())->setURI($paste->getURI());
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
}
$form = new AphrontFormView();
$langs = array(
'' => pht('(Detect From Filename in Title)'),
) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices');
$form
->setUser($viewer)
->addHiddenInput('parent', $parent_id)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Title'))
->setValue($v_title)
->setName('title'))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Language'))
->setName('language')
->setValue($v_language)
->setOptions($langs));
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($paste)
->execute();
$form->appendChild(
id(new AphrontFormPolicyControl())
->setUser($viewer)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($paste)
->setPolicies($policies)
->setValue($v_view_policy)
->setSpacePHID($v_space)
->setName('can_view'));
$form->appendChild(
id(new AphrontFormPolicyControl())
->setUser($viewer)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($paste)
->setPolicies($policies)
->setValue($v_edit_policy)
->setName('can_edit'));
$form->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Status'))
->setName('status')
->setValue($v_status)
->setOptions($paste->getStatusNameMap()));
$form->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Projects'))
->setName('projects')
->setValue($v_projects)
->setDatasource(new PhabricatorProjectDatasource()));
$form
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Text'))
->setValue($v_text)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setCustomClass('PhabricatorMonospaced')
->setName('text'));
$submit = new AphrontFormSubmitControl();
if (!$is_create) {
$submit->addCancelButton($paste->getURI());
$submit->setValue(pht('Save Paste'));
$title = pht('Edit %s', $paste->getFullName());
$short = pht('Edit');
} else {
$submit->setValue(pht('Create Paste'));
$title = pht('Create New Paste');
$short = pht('Create');
}
$form->appendChild($submit);
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
if ($validation_exception) {
$form_box->setValidationException($validation_exception);
}
$crumbs = $this->buildApplicationCrumbs();
if (!$is_create) {
$crumbs->addTextCrumb('P'.$paste->getID(), '/P'.$paste->getID());
}
$crumbs->addTextCrumb($short);
return $this->buildApplicationPage(
array(
$crumbs,
$form_box,
),
array(
'title' => $title,
));
return id(new PhabricatorPasteEditEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -137,9 +137,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController {
$paste,
PhabricatorPolicyCapability::CAN_EDIT);
$can_fork = $viewer->isLoggedIn();
$id = $paste->getID();
$fork_uri = $this->getApplicationURI('/create/?parent='.$id);
return id(new PhabricatorActionListView())
->setUser($viewer)
@ -152,13 +150,6 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController {
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setHref($this->getApplicationURI("edit/{$id}/")))
->addAction(
id(new PhabricatorActionView())
->setName(pht('Fork This Paste'))
->setIcon('fa-code-fork')
->setDisabled(!$can_fork)
->setWorkflow(!$can_fork)
->setHref($fork_uri))
->addAction(
id(new PhabricatorActionView())
->setName(pht('View Raw File'))

View file

@ -0,0 +1,69 @@
<?php
final class PhabricatorPasteEditEngine
extends PhabricatorApplicationEditEngine {
protected function newEditableObject() {
return PhabricatorPaste::initializeNewPaste($this->getViewer());
}
protected function newObjectQuery() {
return id(new PhabricatorPasteQuery())
->needRawContent(true);
}
protected function getObjectCreateTitleText($object) {
return pht('Create New Paste');
}
protected function getObjectEditTitleText($object) {
return pht('Edit %s %s', $object->getMonogram(), $object->getTitle());
}
protected function getObjectEditShortText($object) {
return $object->getMonogram();
}
protected function getObjectCreateShortText($object) {
return pht('Create Paste');
}
protected function getObjectViewURI($object) {
return '/P'.$object->getID();
}
protected function buildCustomEditFields($object) {
$langs = array(
'' => pht('(Detect From Filename in Title)'),
) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices');
return array(
id(new PhabricatorTextEditField())
->setKey('title')
->setLabel(pht('Title'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
->setValue($object->getTitle()),
id(new PhabricatorSelectEditField())
->setKey('language')
->setLabel(pht('Language'))
->setAliases(array('lang'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
->setValue($object->getLanguage())
->setOptions($langs),
id(new PhabricatorSelectEditField())
->setKey('status')
->setLabel(pht('Status'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS)
->setValue($object->getStatus())
->setOptions(PhabricatorPaste::getStatusNameMap()),
id(new PhabricatorTextAreaEditField())
->setKey('text')
->setLabel(pht('Text'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
->setMonospaced(true)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setValue($object->getRawContent()),
);
}
}

View file

@ -3,6 +3,8 @@
final class PhabricatorPasteEditor
extends PhabricatorApplicationTransactionEditor {
private $fileName;
public function getEditorApplicationClass() {
return 'PhabricatorPasteApplication';
}
@ -41,6 +43,35 @@ final class PhabricatorPasteEditor
return $types;
}
protected function shouldApplyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function applyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
// Find the most user-friendly filename we can by examining the title of
// the paste and the pending transactions. We'll use this if we create a
// new file to store raw content later.
$name = $object->getTitle();
if (!strlen($name)) {
$name = 'paste.raw';
}
$type_title = PhabricatorPasteTransaction::TYPE_TITLE;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $type_title) {
$name = $xaction->getNewValue();
}
}
$this->fileName = $name;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
@ -62,11 +93,26 @@ final class PhabricatorPasteEditor
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
case PhabricatorPasteTransaction::TYPE_TITLE:
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
case PhabricatorPasteTransaction::TYPE_STATUS:
return $xaction->getNewValue();
case PhabricatorPasteTransaction::TYPE_CONTENT:
// If this transaction does not really change the paste content, return
// the current file PHID so this transaction no-ops.
$new_content = $xaction->getNewValue();
$old_content = $object->getRawContent();
$file_phid = $object->getFilePHID();
if (($new_content === $old_content) && $file_phid) {
return $file_phid;
}
$file = self::initializeFileForPaste(
$this->getActor(),
$this->fileName,
$xaction->getNewValue());
return $file->getPHID();
}
}

View file

@ -21,16 +21,11 @@ final class PasteCreateMailReceiver extends PhabricatorMailReceiver {
$title = pht('Email Paste');
}
$file = PhabricatorPasteEditor::initializeFileForPaste(
$sender,
$title,
$mail->getCleanTextBody());
$xactions = array();
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT)
->setNewValue($file->getPHID());
->setNewValue($mail->getCleanTextBody());
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)

View file

@ -45,7 +45,8 @@ final class PhabricatorPaste extends PhabricatorPasteDAO
->setAuthorPHID($actor->getPHID())
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->setSpacePHID($actor->getDefaultSpacePHID());
->setSpacePHID($actor->getDefaultSpacePHID())
->attachRawContent(null);
}
public static function getStatusNameMap() {

View file

@ -0,0 +1,303 @@
<?php
abstract class PhabricatorApplicationEditEngine extends Phobject {
private $viewer;
private $controller;
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
final public function setController(PhabricatorController $controller) {
$this->controller = $controller;
$this->setViewer($controller->getViewer());
return $this;
}
final public function getController() {
return $this->controller;
}
final protected function buildEditFields($object) {
$viewer = $this->getViewer();
$editor = $object->getApplicationTransactionEditor();
$types = $editor->getTransactionTypesForObject($object);
$types = array_fuse($types);
$fields = $this->buildCustomEditFields($object);
if ($object instanceof PhabricatorPolicyInterface) {
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($object)
->execute();
$map = array(
PhabricatorTransactions::TYPE_VIEW_POLICY => array(
'key' => 'policy.view',
'aliases' => array('view'),
'capability' => PhabricatorPolicyCapability::CAN_VIEW,
),
PhabricatorTransactions::TYPE_EDIT_POLICY => array(
'key' => 'policy.edit',
'aliases' => array('edit'),
'capability' => PhabricatorPolicyCapability::CAN_EDIT,
),
PhabricatorTransactions::TYPE_JOIN_POLICY => array(
'key' => 'policy.join',
'aliases' => array('join'),
'capability' => PhabricatorPolicyCapability::CAN_JOIN,
),
);
foreach ($map as $type => $spec) {
if (empty($types[$type])) {
continue;
}
$capability = $spec['capability'];
$key = $spec['key'];
$aliases = $spec['aliases'];
$policy_field = id(new PhabricatorPolicyEditField())
->setKey($key)
->setAliases($aliases)
->setCapability($capability)
->setPolicies($policies)
->setTransactionType($type)
->setValue($object->getPolicy($capability));
$fields[] = $policy_field;
if ($object instanceof PhabricatorSpacesInterface) {
if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
$type_space = PhabricatorTransactions::TYPE_SPACE;
if (isset($types[$type_space])) {
$space_field = id(new PhabricatorSpaceEditField())
->setKey('spacePHID')
->setAliases(array('space', 'policy.space'))
->setTransactionType($type_space)
->setValue($object->getSpacePHID());
$fields[] = $space_field;
$policy_field->setSpaceField($space_field);
}
}
}
}
}
$edge_type = PhabricatorTransactions::TYPE_EDGE;
$object_phid = $object->getPHID();
$project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
if ($object instanceof PhabricatorProjectInterface) {
if (isset($types[$edge_type])) {
if ($object_phid) {
$project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object_phid,
$project_edge_type);
$project_phids = array_reverse($project_phids);
} else {
$project_phids = array();
}
$edge_field = id(new PhabricatorDatasourceEditField())
->setKey('projectPHIDs')
->setLabel(pht('Projects'))
->setDatasource(new PhabricatorProjectDatasource())
->setAliases(array('project', 'projects'))
->setTransactionType($edge_type)
->setMetadataValue('edge:type', $project_edge_type)
->setValue($project_phids);
$fields[] = $edge_field;
}
}
$subscribers_type = PhabricatorTransactions::TYPE_SUBSCRIBERS;
if ($object instanceof PhabricatorSubscribableInterface) {
if (isset($types[$subscribers_type])) {
if ($object_phid) {
$sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$object_phid);
} else {
// TODO: Allow applications to provide default subscribers; Maniphest
// does this at a minimum.
$sub_phids = array();
}
$subscribers_field = id(new PhabricatorDatasourceEditField())
->setKey('subscriberPHIDs')
->setLabel(pht('Subscribers'))
->setDatasource(new PhabricatorMetaMTAMailableDatasource())
->setAliases(array('subscriber', 'subscribers'))
->setTransactionType($subscribers_type)
->setValue($sub_phids);
$fields[] = $subscribers_field;
}
}
return $fields;
}
abstract protected function newEditableObject();
abstract protected function newObjectQuery();
abstract protected function buildCustomEditFields($object);
abstract protected function getObjectCreateTitleText($object);
abstract protected function getObjectEditTitleText($object);
abstract protected function getObjectCreateShortText($object);
abstract protected function getObjectEditShortText($object);
abstract protected function getObjectViewURI($object);
protected function getObjectCreateCancelURI($object) {
return $this->getController()->getApplicationURI();
}
protected function getObjectEditCancelURI($object) {
return $this->getObjectViewURI($object);
}
protected function getObjectCreateButtonText($object) {
return $this->getObjectCreateTitleText($object);
}
protected function getObjectEditButtonText($object) {
return pht('Save Changes');
}
final public function buildResponse() {
$controller = $this->getController();
$viewer = $this->getViewer();
$request = $controller->getRequest();
$id = $request->getURIData('id');
if ($id) {
$object = $this->newObjectQuery()
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$object) {
return new Aphront404Response();
}
$is_create = false;
} else {
$object = $this->newEditableObject();
$is_create = true;
}
$fields = $this->buildEditFields($object);
foreach ($fields as $field) {
$field
->setViewer($viewer)
->setObject($object);
}
$validation_exception = null;
if ($request->isFormPost()) {
foreach ($fields as $field) {
$field->readValueFromSubmit($request);
}
$template = $object->getApplicationTransactionTemplate();
$xactions = array();
foreach ($fields as $field) {
$xactions[] = $field->generateTransaction(clone $template);
}
$editor = $object->getApplicationTransactionEditor()
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(false);
try {
$editor->applyTransactions($object, $xactions);
return id(new AphrontRedirectResponse())
->setURI($this->getObjectViewURI($object));
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
}
} else {
if ($is_create) {
foreach ($fields as $field) {
$field->readValueFromRequest($request);
}
} else {
foreach ($fields as $field) {
$field->readValueFromObject($object);
}
}
}
$box = id(new PHUIObjectBoxView())
->setUser($viewer);
$crumbs = $controller->buildApplicationCrumbsForEditEngine();
if ($is_create) {
$header_text = $this->getObjectCreateTitleText($object);
$crumbs->addTextCrumb(
$this->getObjectCreateShortText($object));
$cancel_uri = $this->getObjectCreateCancelURI($object);
$submit_button = $this->getObjectCreateButtonText($object);
} else {
$header_text = $this->getObjectEditTitleText($object);
$crumbs->addTextCrumb(
$this->getObjectEditShortText($object),
$this->getObjectViewURI($object));
$cancel_uri = $this->getObjectEditCancelURI($object);
$submit_button = $this->getObjectEditButtonText($object);
}
$box->setHeaderText($header_text);
$form = id(new AphrontFormView())
->setUser($viewer);
foreach ($fields as $field) {
$field->appendToForm($form);
}
$form->appendControl(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($submit_button));
$box->appendChild($form);
if ($validation_exception) {
$box->setValidationException($validation_exception);
}
return $controller->newPage()
->setTitle($header_text)
->setCrumbs($crumbs)
->appendChild($box);
}
}

View file

@ -0,0 +1,21 @@
<?php
final class PhabricatorDatasourceEditField
extends PhabricatorTokenizerEditField {
private $datasource;
public function setDatasource(PhabricatorTypeaheadDatasource $datasource) {
$this->datasource = $datasource;
return $this;
}
public function getDatasource() {
return $this->datasource;
}
protected function newDatasource() {
return id(clone $this->getDatasource());
}
}

View file

@ -0,0 +1,212 @@
<?php
abstract class PhabricatorEditField extends Phobject {
private $key;
private $viewer;
private $label;
private $aliases = array();
private $value;
private $hasValue = false;
private $object;
private $transactionType;
private $metadata = array();
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setLabel($label) {
$this->label = $label;
return $this;
}
public function getLabel() {
return $this->label;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setAliases(array $aliases) {
$this->aliases = $aliases;
return $this;
}
public function getAliases() {
return $this->aliases;
}
public function setObject($object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->object;
}
abstract protected function newControl();
protected function renderControl() {
$control = $this->newControl();
if ($control === null) {
return null;
}
$control
->setValue($this->getValueForControl())
->setName($this->getKey());
if (!$control->getLabel()) {
$control->setLabel($this->getLabel());
}
return $control;
}
public function appendToForm(AphrontFormView $form) {
$control = $this->renderControl();
if ($control !== null) {
$form->appendControl($control);
}
return $this;
}
protected function getValueForControl() {
return $this->getValue();
}
protected function getValue() {
return $this->value;
}
public function setValue($value) {
$this->hasValue = true;
$this->value = $value;
return $this;
}
public function generateTransaction(
PhabricatorApplicationTransaction $xaction) {
$xaction
->setTransactionType($this->getTransactionType())
->setNewValue($this->getValueForTransaction());
foreach ($this->metadata as $key => $value) {
$xaction->setMetadataValue($key, $value);
}
return $xaction;
}
public function setMetadataValue($key, $value) {
$this->metadata[$key] = $value;
return $this;
}
protected function getValueForTransaction() {
return $this->getValue();
}
public function getTransactionType() {
if (!$this->transactionType) {
throw new PhutilInvalidStateException('setTransactionType');
}
return $this->transactionType;
}
public function setTransactionType($type) {
$this->transactionType = $type;
return $this;
}
public function readValueFromRequest(AphrontRequest $request) {
$check = array_merge(array($this->getKey()), $this->getAliases());
foreach ($check as $key) {
if (!$this->getValueExistsInRequest($request, $key)) {
continue;
}
$this->value = $this->getValueFromRequest($request, $key);
return;
}
$this->readValueFromObject($this->getObject());
return $this;
}
public function readValueFromObject($object) {
$this->value = $this->getValueFromObject($object);
return $this;
}
protected function getValueFromObject($object) {
if ($this->hasValue) {
return $this->value;
} else {
return $this->getDefaultValue();
}
}
protected function getValueExistsInRequest(AphrontRequest $request, $key) {
return $this->getValueExistsInSubmit($request, $key);
}
protected function getValueFromRequest(AphrontRequest $request, $key) {
return $this->getValueFromSubmit($request, $key);
}
public function readValueFromSubmit(AphrontRequest $request) {
$key = $this->getKey();
if ($this->getValueExistsInSubmit($request, $key)) {
$value = $this->getValueFromSubmit($request, $key);
} else {
$value = $this->getDefaultValue();
}
$this->value = $value;
return $this;
}
protected function getValueExistsInSubmit(AphrontRequest $request, $key) {
return $request->getExists($key);
}
protected function getValueFromSubmit(AphrontRequest $request, $key) {
return $request->getStr($key);
}
protected function getDefaultValue() {
return null;
}
protected function getListFromRequest(
AphrontRequest $request,
$key) {
$list = $request->getArr($key, null);
if ($list === null) {
$list = $request->getStrList($key);
}
if (!$list) {
return array();
}
return $list;
}
}

View file

@ -0,0 +1,54 @@
<?php
final class PhabricatorPolicyEditField
extends PhabricatorEditField {
private $policies;
private $capability;
private $spaceField;
public function setPolicies(array $policies) {
$this->policies = $policies;
return $this;
}
public function getPolicies() {
if ($this->policies === null) {
throw new PhutilInvalidStateException('setPolicies');
}
return $this->policies;
}
public function setCapability($capability) {
$this->capability = $capability;
return $this;
}
public function getCapability() {
return $this->capability;
}
public function setSpaceField(PhabricatorSpaceEditField $space_field) {
$this->spaceField = $space_field;
return $this;
}
public function getSpaceField() {
return $this->spaceField;
}
protected function newControl() {
$control = id(new AphrontFormPolicyControl())
->setCapability($this->getCapability())
->setPolicyObject($this->getObject())
->setPolicies($this->getPolicies());
$space_field = $this->getSpaceField();
if ($space_field) {
$control->setSpacePHID($space_field->getValueForControl());
}
return $control;
}
}

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorSelectEditField
extends PhabricatorEditField {
private $options;
public function setOptions(array $options) {
$this->options = $options;
return $this;
}
public function getOptions() {
if ($this->options === null) {
throw new PhutilInvalidStateException('setOptions');
}
return $this->options;
}
protected function newControl() {
return id(new AphrontFormSelectControl())
->setOptions($this->getOptions());
}
}

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorSpaceEditField
extends PhabricatorEditField {
protected function newControl() {
// NOTE: This field doesn't do anything on its own, it just serves as a
// companion to the associated View Policy field.
return null;
}
}

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorTextAreaEditField
extends PhabricatorEditField {
private $monospaced;
private $height;
public function setMonospaced($monospaced) {
$this->monospaced = $monospaced;
return $this;
}
public function getMonospaced() {
return $this->monospaced;
}
public function setHeight($height) {
$this->height = $height;
return $this;
}
public function getHeight() {
return $this->height;
}
protected function newControl() {
$control = new AphrontFormTextAreaControl();
if ($this->getMonospaced()) {
$control->setCustomClass('PhabricatorMonospaced');
}
$height = $this->getHeight();
if ($height) {
$control->setHeight($height);
}
return $control;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhabricatorTextEditField
extends PhabricatorEditField {
protected function newControl() {
return new AphrontFormTextControl();
}
}

View file

@ -0,0 +1,90 @@
<?php
abstract class PhabricatorTokenizerEditField
extends PhabricatorEditField {
private $originalValue;
abstract protected function newDatasource();
protected function newControl() {
$control = id(new AphrontFormTokenizerControl())
->setDatasource($this->newDatasource());
if ($this->originalValue !== null) {
$control->setOriginalValue($this->originalValue);
}
return $control;
}
public function setOriginalValue(array $value) {
$this->originalValue = $value;
return $this;
}
public function setValue($value) {
$this->originalValue = $value;
return parent::setValue($value);
}
protected function getValueFromSubmit(AphrontRequest $request, $key) {
// TODO: Maybe move this unusual read somewhere else so subclassing this
// correctly is easier?
$this->originalValue = $request->getArr($key.'.original');
return $this->getListFromRequest($request, $key);
}
protected function getDefaultValue() {
return array();
}
protected function getValueForTransaction() {
$new = parent::getValueForTransaction();
$edge_types = array(
PhabricatorTransactions::TYPE_EDGE => true,
PhabricatorTransactions::TYPE_SUBSCRIBERS => true,
);
if (isset($edge_types[$this->getTransactionType()])) {
if ($this->originalValue !== null) {
// If we're building an edge transaction and the request has data
// about the original value the user saw when they loaded the form,
// interpret the edit as a mixture of "+" and "-" operations instead
// of a single "=" operation. This limits our exposure to race
// conditions by making most concurrent edits merge correctly.
$new = parent::getValueForTransaction();
$old = $this->originalValue;
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
$value = array();
if ($add) {
$value['+'] = array_fuse($add);
}
if ($rem) {
$value['-'] = array_fuse($rem);
}
return $value;
} else {
if (!is_array($new)) {
throw new Exception(print_r($new, true));
}
return array(
'=' => array_fuse($new),
);
}
}
return $new;
}
}

View file

@ -242,6 +242,19 @@ abstract class PhabricatorApplicationTransactionEditor
return $this->applicationEmail;
}
public function getTransactionTypesForObject($object) {
$old = $this->object;
try {
$this->object = $object;
$result = $this->getTransactionTypes();
$this->object = $old;
} catch (Exception $ex) {
$this->object = $old;
throw $ex;
}
return $result;
}
public function getTransactionTypes() {
$types = array();
@ -1650,7 +1663,7 @@ abstract class PhabricatorApplicationTransactionEditor
throw new Exception(
pht(
"Invalid '%s' value for PHID transaction. Value should contain only ".
"keys '%s' (add PHIDs), '%' (remove PHIDs) and '%s' (set PHIDS).",
"keys '%s' (add PHIDs), '%s' (remove PHIDs) and '%s' (set PHIDS).",
'new',
'+',
'-',

View file

@ -6,6 +6,7 @@ final class AphrontTokenizerTemplateView extends AphrontView {
private $name;
private $id;
private $browseURI;
private $originalValue;
public function setBrowseURI($browse_uri) {
$this->browseURI = $browse_uri;
@ -36,6 +37,15 @@ final class AphrontTokenizerTemplateView extends AphrontView {
return $this->name;
}
public function setOriginalValue(array $original_value) {
$this->originalValue = $original_value;
return $this;
}
public function getOriginalValue() {
return $this->originalValue;
}
public function render() {
require_celerity_resource('aphront-tokenizer-control-css');
@ -85,6 +95,20 @@ final class AphrontTokenizerTemplateView extends AphrontView {
$classes[] = 'has-browse';
}
$original = array();
$original_value = $this->getOriginalValue();
if ($original_value) {
foreach ($this->getOriginalValue() as $value) {
$original[] = phutil_tag(
'input',
array(
'type' => 'hidden',
'name' => $name.'.original[]',
'value' => $value,
));
}
}
$frame = javelin_tag(
'div',
array(
@ -94,6 +118,7 @@ final class AphrontTokenizerTemplateView extends AphrontView {
array(
$container,
$browse,
$original,
));
return $frame;

View file

@ -7,6 +7,7 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
private $limit;
private $placeholder;
private $handles;
private $originalValue;
public function setDatasource(PhabricatorTypeaheadDatasource $datasource) {
$this->datasource = $datasource;
@ -32,6 +33,15 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
return $this;
}
public function setOriginalValue(array $original_value) {
$this->originalValue = $original_value;
return $this;
}
public function getOriginalValue() {
return $this->originalValue;
}
public function willRender() {
// Load the handles now so we'll get a bulk load later on when we actually
// render them.
@ -69,10 +79,15 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
$token->setInputName($this->getName());
}
$template = new AphrontTokenizerTemplateView();
$template->setName($name);
$template->setID($id);
$template->setValue($tokens);
$template = id(new AphrontTokenizerTemplateView())
->setName($name)
->setID($id)
->setValue($tokens);
$original_value = $this->getOriginalValue();
if ($original_value !== null) {
$template->setOriginalValue($original_value);
}
$username = null;
if ($this->user) {