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

Configure Feed

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

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

+978 -270
+20
src/__phutil_library_map__.php
··· 1570 1570 'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php', 1571 1571 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 1572 1572 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', 1573 + 'PhabricatorApplicationEditEngine' => 'applications/transactions/editengine/PhabricatorApplicationEditEngine.php', 1573 1574 'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php', 1574 1575 'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php', 1575 1576 'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php', ··· 2063 2064 'PhabricatorDataCacheSpec' => 'applications/cache/spec/PhabricatorDataCacheSpec.php', 2064 2065 'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php', 2065 2066 'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php', 2067 + 'PhabricatorDatasourceEditField' => 'applications/transactions/editfield/PhabricatorDatasourceEditField.php', 2066 2068 'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php', 2067 2069 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', 2068 2070 'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php', ··· 2097 2099 'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php', 2098 2100 'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php', 2099 2101 'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php', 2102 + 'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php', 2100 2103 'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php', 2101 2104 'PhabricatorElasticSearchEngine' => 'applications/search/engine/PhabricatorElasticSearchEngine.php', 2102 2105 'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php', ··· 2554 2557 'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php', 2555 2558 'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php', 2556 2559 'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php', 2560 + 'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php', 2557 2561 'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php', 2558 2562 'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php', 2559 2563 'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php', ··· 2654 2658 'PhabricatorPolicyDAO' => 'applications/policy/storage/PhabricatorPolicyDAO.php', 2655 2659 'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php', 2656 2660 'PhabricatorPolicyEditController' => 'applications/policy/controller/PhabricatorPolicyEditController.php', 2661 + 'PhabricatorPolicyEditField' => 'applications/transactions/editfield/PhabricatorPolicyEditField.php', 2657 2662 'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php', 2658 2663 'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php', 2659 2664 'PhabricatorPolicyFilter' => 'applications/policy/filter/PhabricatorPolicyFilter.php', ··· 2917 2922 'PhabricatorSearchWorker' => 'applications/search/worker/PhabricatorSearchWorker.php', 2918 2923 'PhabricatorSecurityConfigOptions' => 'applications/config/option/PhabricatorSecurityConfigOptions.php', 2919 2924 'PhabricatorSecuritySetupCheck' => 'applications/config/check/PhabricatorSecuritySetupCheck.php', 2925 + 'PhabricatorSelectEditField' => 'applications/transactions/editfield/PhabricatorSelectEditField.php', 2920 2926 'PhabricatorSendGridConfigOptions' => 'applications/config/option/PhabricatorSendGridConfigOptions.php', 2921 2927 'PhabricatorSessionsSettingsPanel' => 'applications/settings/panel/PhabricatorSessionsSettingsPanel.php', 2922 2928 'PhabricatorSettingsAddEmailAction' => 'applications/settings/action/PhabricatorSettingsAddEmailAction.php', ··· 2957 2963 'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php', 2958 2964 'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php', 2959 2965 'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php', 2966 + 'PhabricatorSpaceEditField' => 'applications/transactions/editfield/PhabricatorSpaceEditField.php', 2960 2967 'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php', 2961 2968 'PhabricatorSpacesArchiveController' => 'applications/spaces/controller/PhabricatorSpacesArchiveController.php', 2962 2969 'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php', ··· 3063 3070 'PhabricatorTestNoCycleEdgeType' => 'applications/transactions/edges/PhabricatorTestNoCycleEdgeType.php', 3064 3071 'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php', 3065 3072 'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php', 3073 + 'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php', 3074 + 'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php', 3066 3075 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', 3067 3076 'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php', 3068 3077 'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php', ··· 3084 3093 'PhabricatorTokenReceiverQuery' => 'applications/tokens/query/PhabricatorTokenReceiverQuery.php', 3085 3094 'PhabricatorTokenTokenPHIDType' => 'applications/tokens/phid/PhabricatorTokenTokenPHIDType.php', 3086 3095 'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php', 3096 + 'PhabricatorTokenizerEditField' => 'applications/transactions/editfield/PhabricatorTokenizerEditField.php', 3087 3097 'PhabricatorTokensApplication' => 'applications/tokens/application/PhabricatorTokensApplication.php', 3088 3098 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 3089 3099 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', ··· 5489 5499 'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource', 5490 5500 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 5491 5501 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', 5502 + 'PhabricatorApplicationEditEngine' => 'Phobject', 5492 5503 'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController', 5493 5504 'PhabricatorApplicationLaunchView' => 'AphrontTagView', 5494 5505 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', ··· 6080 6091 'PhabricatorDataCacheSpec' => 'PhabricatorCacheSpec', 6081 6092 'PhabricatorDataNotAttachedException' => 'Exception', 6082 6093 'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck', 6094 + 'PhabricatorDatasourceEditField' => 'PhabricatorTokenizerEditField', 6083 6095 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel', 6084 6096 'PhabricatorDebugController' => 'PhabricatorController', 6085 6097 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', ··· 6113 6125 'PhabricatorEdgeTestCase' => 'PhabricatorTestCase', 6114 6126 'PhabricatorEdgeType' => 'Phobject', 6115 6127 'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase', 6128 + 'PhabricatorEditField' => 'Phobject', 6116 6129 'PhabricatorEditor' => 'Phobject', 6117 6130 'PhabricatorElasticSearchEngine' => 'PhabricatorSearchEngine', 6118 6131 'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck', ··· 6647 6660 'PhabricatorPasteController' => 'PhabricatorController', 6648 6661 'PhabricatorPasteDAO' => 'PhabricatorLiskDAO', 6649 6662 'PhabricatorPasteEditController' => 'PhabricatorPasteController', 6663 + 'PhabricatorPasteEditEngine' => 'PhabricatorApplicationEditEngine', 6650 6664 'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor', 6651 6665 'PhabricatorPasteListController' => 'PhabricatorPasteController', 6652 6666 'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType', ··· 6762 6776 'PhabricatorPolicyDAO' => 'PhabricatorLiskDAO', 6763 6777 'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase', 6764 6778 'PhabricatorPolicyEditController' => 'PhabricatorPolicyController', 6779 + 'PhabricatorPolicyEditField' => 'PhabricatorEditField', 6765 6780 'PhabricatorPolicyException' => 'Exception', 6766 6781 'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController', 6767 6782 'PhabricatorPolicyFilter' => 'Phobject', ··· 7090 7105 'PhabricatorSearchWorker' => 'PhabricatorWorker', 7091 7106 'PhabricatorSecurityConfigOptions' => 'PhabricatorApplicationConfigOptions', 7092 7107 'PhabricatorSecuritySetupCheck' => 'PhabricatorSetupCheck', 7108 + 'PhabricatorSelectEditField' => 'PhabricatorEditField', 7093 7109 'PhabricatorSendGridConfigOptions' => 'PhabricatorApplicationConfigOptions', 7094 7110 'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel', 7095 7111 'PhabricatorSettingsAddEmailAction' => 'PhabricatorSystemAction', ··· 7140 7156 'PhabricatorSlugTestCase' => 'PhabricatorTestCase', 7141 7157 'PhabricatorSortTableUIExample' => 'PhabricatorUIExample', 7142 7158 'PhabricatorSourceCodeView' => 'AphrontView', 7159 + 'PhabricatorSpaceEditField' => 'PhabricatorEditField', 7143 7160 'PhabricatorSpacesApplication' => 'PhabricatorApplication', 7144 7161 'PhabricatorSpacesArchiveController' => 'PhabricatorSpacesController', 7145 7162 'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability', ··· 7252 7269 'PhabricatorTestNoCycleEdgeType' => 'PhabricatorEdgeType', 7253 7270 'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine', 7254 7271 'PhabricatorTestWorker' => 'PhabricatorWorker', 7272 + 'PhabricatorTextAreaEditField' => 'PhabricatorEditField', 7273 + 'PhabricatorTextEditField' => 'PhabricatorEditField', 7255 7274 'PhabricatorTime' => 'Phobject', 7256 7275 'PhabricatorTimeGuard' => 'Phobject', 7257 7276 'PhabricatorTimeTestCase' => 'PhabricatorTestCase', ··· 7278 7297 'PhabricatorTokenReceiverQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 7279 7298 'PhabricatorTokenTokenPHIDType' => 'PhabricatorPHIDType', 7280 7299 'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener', 7300 + 'PhabricatorTokenizerEditField' => 'PhabricatorEditField', 7281 7301 'PhabricatorTokensApplication' => 'PhabricatorApplication', 7282 7302 'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel', 7283 7303 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample',
+8
src/applications/base/controller/PhabricatorController.php
··· 521 521 } 522 522 523 523 524 + public function buildApplicationCrumbsForEditEngine() { 525 + // TODO: This is kind of gross, I'm bascially just making this public so 526 + // I can use it in EditEngine. We could do this without making it public 527 + // by using controller delegation, or make it properly public. 528 + return $this->buildApplicationCrumbs(); 529 + } 530 + 531 + 524 532 /* -( Deprecated )--------------------------------------------------------- */ 525 533 526 534
+1 -6
src/applications/paste/conduit/PasteCreateConduitAPIMethod.php
··· 44 44 45 45 $paste = PhabricatorPaste::initializeNewPaste($viewer); 46 46 47 - $file = PhabricatorPasteEditor::initializeFileForPaste( 48 - $viewer, 49 - $title, 50 - $content); 51 - 52 47 $xactions = array(); 53 48 54 49 $xactions[] = id(new PhabricatorPasteTransaction()) 55 50 ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) 56 - ->setNewValue($file->getPHID()); 51 + ->setNewValue($content); 57 52 58 53 $xactions[] = id(new PhabricatorPasteTransaction()) 59 54 ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
+3 -242
src/applications/paste/controller/PhabricatorPasteEditController.php
··· 3 3 final class PhabricatorPasteEditController extends PhabricatorPasteController { 4 4 5 5 public function handleRequest(AphrontRequest $request) { 6 - $viewer = $request->getViewer(); 7 - $id = $request->getURIData('id'); 8 - 9 - $parent = null; 10 - $parent_id = null; 11 - if (!$id) { 12 - $is_create = true; 13 - 14 - $paste = PhabricatorPaste::initializeNewPaste($viewer); 15 - 16 - $parent_id = $request->getStr('parent'); 17 - if ($parent_id) { 18 - // NOTE: If the Paste is forked from a paste which the user no longer 19 - // has permission to see, we still let them edit it. 20 - $parent = id(new PhabricatorPasteQuery()) 21 - ->setViewer($viewer) 22 - ->withIDs(array($parent_id)) 23 - ->needContent(true) 24 - ->needRawContent(true) 25 - ->execute(); 26 - $parent = head($parent); 27 - 28 - if ($parent) { 29 - $paste->setParentPHID($parent->getPHID()); 30 - $paste->setViewPolicy($parent->getViewPolicy()); 31 - } 32 - } 33 - 34 - $paste->setAuthorPHID($viewer->getPHID()); 35 - $paste->attachRawContent(''); 36 - } else { 37 - $is_create = false; 38 - 39 - $paste = id(new PhabricatorPasteQuery()) 40 - ->setViewer($viewer) 41 - ->requireCapabilities( 42 - array( 43 - PhabricatorPolicyCapability::CAN_VIEW, 44 - PhabricatorPolicyCapability::CAN_EDIT, 45 - )) 46 - ->withIDs(array($id)) 47 - ->needRawContent(true) 48 - ->executeOne(); 49 - if (!$paste) { 50 - return new Aphront404Response(); 51 - } 52 - } 53 - 54 - $v_space = $paste->getSpacePHID(); 55 - if ($is_create && $parent) { 56 - $v_title = pht('Fork of %s', $parent->getFullName()); 57 - $v_language = $parent->getLanguage(); 58 - $v_text = $parent->getRawContent(); 59 - $v_space = $parent->getSpacePHID(); 60 - } else { 61 - $v_title = $paste->getTitle(); 62 - $v_language = $paste->getLanguage(); 63 - $v_text = $paste->getRawContent(); 64 - } 65 - $v_view_policy = $paste->getViewPolicy(); 66 - $v_edit_policy = $paste->getEditPolicy(); 67 - $v_status = $paste->getStatus(); 68 - 69 - if ($is_create) { 70 - $v_projects = array(); 71 - } else { 72 - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( 73 - $paste->getPHID(), 74 - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); 75 - $v_projects = array_reverse($v_projects); 76 - } 77 - 78 - $validation_exception = null; 79 - if ($request->isFormPost()) { 80 - $xactions = array(); 81 - 82 - $v_text = $request->getStr('text'); 83 - $v_title = $request->getStr('title'); 84 - $v_language = $request->getStr('language'); 85 - $v_view_policy = $request->getStr('can_view'); 86 - $v_edit_policy = $request->getStr('can_edit'); 87 - $v_projects = $request->getArr('projects'); 88 - $v_space = $request->getStr('spacePHID'); 89 - $v_status = $request->getStr('status'); 90 - 91 - // NOTE: The author is the only editor and can always view the paste, 92 - // so it's impossible for them to choose an invalid policy. 93 - 94 - if ($is_create || ($v_text !== $paste->getRawContent())) { 95 - $file = PhabricatorPasteEditor::initializeFileForPaste( 96 - $viewer, 97 - $v_title, 98 - $v_text); 99 - 100 - $xactions[] = id(new PhabricatorPasteTransaction()) 101 - ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) 102 - ->setNewValue($file->getPHID()); 103 - } 104 - 105 - $xactions[] = id(new PhabricatorPasteTransaction()) 106 - ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) 107 - ->setNewValue($v_title); 108 - $xactions[] = id(new PhabricatorPasteTransaction()) 109 - ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) 110 - ->setNewValue($v_language); 111 - $xactions[] = id(new PhabricatorPasteTransaction()) 112 - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) 113 - ->setNewValue($v_view_policy); 114 - $xactions[] = id(new PhabricatorPasteTransaction()) 115 - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) 116 - ->setNewValue($v_edit_policy); 117 - $xactions[] = id(new PhabricatorPasteTransaction()) 118 - ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) 119 - ->setNewValue($v_space); 120 - $xactions[] = id(new PhabricatorPasteTransaction()) 121 - ->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) 122 - ->setNewValue($v_status); 123 - 124 - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 125 - $xactions[] = id(new PhabricatorPasteTransaction()) 126 - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 127 - ->setMetadataValue('edge:type', $proj_edge_type) 128 - ->setNewValue(array('=' => array_fuse($v_projects))); 129 - 130 - $editor = id(new PhabricatorPasteEditor()) 131 - ->setActor($viewer) 132 - ->setContentSourceFromRequest($request) 133 - ->setContinueOnNoEffect(true); 134 - 135 - try { 136 - $xactions = $editor->applyTransactions($paste, $xactions); 137 - return id(new AphrontRedirectResponse())->setURI($paste->getURI()); 138 - } catch (PhabricatorApplicationTransactionValidationException $ex) { 139 - $validation_exception = $ex; 140 - } 141 - } 142 - 143 - $form = new AphrontFormView(); 144 - 145 - $langs = array( 146 - '' => pht('(Detect From Filename in Title)'), 147 - ) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices'); 148 - 149 - $form 150 - ->setUser($viewer) 151 - ->addHiddenInput('parent', $parent_id) 152 - ->appendChild( 153 - id(new AphrontFormTextControl()) 154 - ->setLabel(pht('Title')) 155 - ->setValue($v_title) 156 - ->setName('title')) 157 - ->appendChild( 158 - id(new AphrontFormSelectControl()) 159 - ->setLabel(pht('Language')) 160 - ->setName('language') 161 - ->setValue($v_language) 162 - ->setOptions($langs)); 163 - 164 - $policies = id(new PhabricatorPolicyQuery()) 165 - ->setViewer($viewer) 166 - ->setObject($paste) 167 - ->execute(); 168 - 169 - $form->appendChild( 170 - id(new AphrontFormPolicyControl()) 171 - ->setUser($viewer) 172 - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) 173 - ->setPolicyObject($paste) 174 - ->setPolicies($policies) 175 - ->setValue($v_view_policy) 176 - ->setSpacePHID($v_space) 177 - ->setName('can_view')); 178 - 179 - $form->appendChild( 180 - id(new AphrontFormPolicyControl()) 181 - ->setUser($viewer) 182 - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) 183 - ->setPolicyObject($paste) 184 - ->setPolicies($policies) 185 - ->setValue($v_edit_policy) 186 - ->setName('can_edit')); 187 - 188 - $form->appendChild( 189 - id(new AphrontFormSelectControl()) 190 - ->setLabel(pht('Status')) 191 - ->setName('status') 192 - ->setValue($v_status) 193 - ->setOptions($paste->getStatusNameMap())); 194 - 195 - $form->appendControl( 196 - id(new AphrontFormTokenizerControl()) 197 - ->setLabel(pht('Projects')) 198 - ->setName('projects') 199 - ->setValue($v_projects) 200 - ->setDatasource(new PhabricatorProjectDatasource())); 201 - 202 - $form 203 - ->appendChild( 204 - id(new AphrontFormTextAreaControl()) 205 - ->setLabel(pht('Text')) 206 - ->setValue($v_text) 207 - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) 208 - ->setCustomClass('PhabricatorMonospaced') 209 - ->setName('text')); 210 - 211 - $submit = new AphrontFormSubmitControl(); 212 - 213 - if (!$is_create) { 214 - $submit->addCancelButton($paste->getURI()); 215 - $submit->setValue(pht('Save Paste')); 216 - $title = pht('Edit %s', $paste->getFullName()); 217 - $short = pht('Edit'); 218 - } else { 219 - $submit->setValue(pht('Create Paste')); 220 - $title = pht('Create New Paste'); 221 - $short = pht('Create'); 222 - } 223 - 224 - $form->appendChild($submit); 225 - 226 - $form_box = id(new PHUIObjectBoxView()) 227 - ->setHeaderText($title) 228 - ->setForm($form); 229 - 230 - if ($validation_exception) { 231 - $form_box->setValidationException($validation_exception); 232 - } 233 - 234 - $crumbs = $this->buildApplicationCrumbs(); 235 - if (!$is_create) { 236 - $crumbs->addTextCrumb('P'.$paste->getID(), '/P'.$paste->getID()); 237 - } 238 - $crumbs->addTextCrumb($short); 239 - 240 - return $this->buildApplicationPage( 241 - array( 242 - $crumbs, 243 - $form_box, 244 - ), 245 - array( 246 - 'title' => $title, 247 - )); 6 + return id(new PhabricatorPasteEditEngine()) 7 + ->setController($this) 8 + ->buildResponse(); 248 9 } 249 10 250 11 }
-9
src/applications/paste/controller/PhabricatorPasteViewController.php
··· 137 137 $paste, 138 138 PhabricatorPolicyCapability::CAN_EDIT); 139 139 140 - $can_fork = $viewer->isLoggedIn(); 141 140 $id = $paste->getID(); 142 - $fork_uri = $this->getApplicationURI('/create/?parent='.$id); 143 141 144 142 return id(new PhabricatorActionListView()) 145 143 ->setUser($viewer) ··· 152 150 ->setDisabled(!$can_edit) 153 151 ->setWorkflow(!$can_edit) 154 152 ->setHref($this->getApplicationURI("edit/{$id}/"))) 155 - ->addAction( 156 - id(new PhabricatorActionView()) 157 - ->setName(pht('Fork This Paste')) 158 - ->setIcon('fa-code-fork') 159 - ->setDisabled(!$can_fork) 160 - ->setWorkflow(!$can_fork) 161 - ->setHref($fork_uri)) 162 153 ->addAction( 163 154 id(new PhabricatorActionView()) 164 155 ->setName(pht('View Raw File'))
+69
src/applications/paste/editor/PhabricatorPasteEditEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorPasteEditEngine 4 + extends PhabricatorApplicationEditEngine { 5 + 6 + protected function newEditableObject() { 7 + return PhabricatorPaste::initializeNewPaste($this->getViewer()); 8 + } 9 + 10 + protected function newObjectQuery() { 11 + return id(new PhabricatorPasteQuery()) 12 + ->needRawContent(true); 13 + } 14 + 15 + protected function getObjectCreateTitleText($object) { 16 + return pht('Create New Paste'); 17 + } 18 + 19 + protected function getObjectEditTitleText($object) { 20 + return pht('Edit %s %s', $object->getMonogram(), $object->getTitle()); 21 + } 22 + 23 + protected function getObjectEditShortText($object) { 24 + return $object->getMonogram(); 25 + } 26 + 27 + protected function getObjectCreateShortText($object) { 28 + return pht('Create Paste'); 29 + } 30 + 31 + protected function getObjectViewURI($object) { 32 + return '/P'.$object->getID(); 33 + } 34 + 35 + protected function buildCustomEditFields($object) { 36 + $langs = array( 37 + '' => pht('(Detect From Filename in Title)'), 38 + ) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices'); 39 + 40 + return array( 41 + id(new PhabricatorTextEditField()) 42 + ->setKey('title') 43 + ->setLabel(pht('Title')) 44 + ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) 45 + ->setValue($object->getTitle()), 46 + id(new PhabricatorSelectEditField()) 47 + ->setKey('language') 48 + ->setLabel(pht('Language')) 49 + ->setAliases(array('lang')) 50 + ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) 51 + ->setValue($object->getLanguage()) 52 + ->setOptions($langs), 53 + id(new PhabricatorSelectEditField()) 54 + ->setKey('status') 55 + ->setLabel(pht('Status')) 56 + ->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) 57 + ->setValue($object->getStatus()) 58 + ->setOptions(PhabricatorPaste::getStatusNameMap()), 59 + id(new PhabricatorTextAreaEditField()) 60 + ->setKey('text') 61 + ->setLabel(pht('Text')) 62 + ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) 63 + ->setMonospaced(true) 64 + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) 65 + ->setValue($object->getRawContent()), 66 + ); 67 + } 68 + 69 + }
+47 -1
src/applications/paste/editor/PhabricatorPasteEditor.php
··· 3 3 final class PhabricatorPasteEditor 4 4 extends PhabricatorApplicationTransactionEditor { 5 5 6 + private $fileName; 7 + 6 8 public function getEditorApplicationClass() { 7 9 return 'PhabricatorPasteApplication'; 8 10 } ··· 41 43 return $types; 42 44 } 43 45 46 + protected function shouldApplyInitialEffects( 47 + PhabricatorLiskDAO $object, 48 + array $xactions) { 49 + return true; 50 + } 51 + 52 + protected function applyInitialEffects( 53 + PhabricatorLiskDAO $object, 54 + array $xactions) { 55 + 56 + // Find the most user-friendly filename we can by examining the title of 57 + // the paste and the pending transactions. We'll use this if we create a 58 + // new file to store raw content later. 59 + 60 + $name = $object->getTitle(); 61 + if (!strlen($name)) { 62 + $name = 'paste.raw'; 63 + } 64 + 65 + $type_title = PhabricatorPasteTransaction::TYPE_TITLE; 66 + foreach ($xactions as $xaction) { 67 + if ($xaction->getTransactionType() == $type_title) { 68 + $name = $xaction->getNewValue(); 69 + } 70 + } 71 + 72 + $this->fileName = $name; 73 + } 74 + 44 75 protected function getCustomTransactionOldValue( 45 76 PhabricatorLiskDAO $object, 46 77 PhabricatorApplicationTransaction $xaction) { ··· 62 93 PhabricatorApplicationTransaction $xaction) { 63 94 64 95 switch ($xaction->getTransactionType()) { 65 - case PhabricatorPasteTransaction::TYPE_CONTENT: 66 96 case PhabricatorPasteTransaction::TYPE_TITLE: 67 97 case PhabricatorPasteTransaction::TYPE_LANGUAGE: 68 98 case PhabricatorPasteTransaction::TYPE_STATUS: 69 99 return $xaction->getNewValue(); 100 + case PhabricatorPasteTransaction::TYPE_CONTENT: 101 + // If this transaction does not really change the paste content, return 102 + // the current file PHID so this transaction no-ops. 103 + $new_content = $xaction->getNewValue(); 104 + $old_content = $object->getRawContent(); 105 + $file_phid = $object->getFilePHID(); 106 + if (($new_content === $old_content) && $file_phid) { 107 + return $file_phid; 108 + } 109 + 110 + $file = self::initializeFileForPaste( 111 + $this->getActor(), 112 + $this->fileName, 113 + $xaction->getNewValue()); 114 + 115 + return $file->getPHID(); 70 116 } 71 117 } 72 118
+1 -6
src/applications/paste/mail/PasteCreateMailReceiver.php
··· 21 21 $title = pht('Email Paste'); 22 22 } 23 23 24 - $file = PhabricatorPasteEditor::initializeFileForPaste( 25 - $sender, 26 - $title, 27 - $mail->getCleanTextBody()); 28 - 29 24 $xactions = array(); 30 25 31 26 $xactions[] = id(new PhabricatorPasteTransaction()) 32 27 ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) 33 - ->setNewValue($file->getPHID()); 28 + ->setNewValue($mail->getCleanTextBody()); 34 29 35 30 $xactions[] = id(new PhabricatorPasteTransaction()) 36 31 ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE)
+2 -1
src/applications/paste/storage/PhabricatorPaste.php
··· 45 45 ->setAuthorPHID($actor->getPHID()) 46 46 ->setViewPolicy($view_policy) 47 47 ->setEditPolicy($edit_policy) 48 - ->setSpacePHID($actor->getDefaultSpacePHID()); 48 + ->setSpacePHID($actor->getDefaultSpacePHID()) 49 + ->attachRawContent(null); 49 50 } 50 51 51 52 public static function getStatusNameMap() {
+303
src/applications/transactions/editengine/PhabricatorApplicationEditEngine.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorApplicationEditEngine extends Phobject { 4 + 5 + private $viewer; 6 + private $controller; 7 + 8 + final public function setViewer(PhabricatorUser $viewer) { 9 + $this->viewer = $viewer; 10 + return $this; 11 + } 12 + 13 + final public function getViewer() { 14 + return $this->viewer; 15 + } 16 + 17 + final public function setController(PhabricatorController $controller) { 18 + $this->controller = $controller; 19 + $this->setViewer($controller->getViewer()); 20 + return $this; 21 + } 22 + 23 + final public function getController() { 24 + return $this->controller; 25 + } 26 + 27 + final protected function buildEditFields($object) { 28 + $viewer = $this->getViewer(); 29 + $editor = $object->getApplicationTransactionEditor(); 30 + 31 + $types = $editor->getTransactionTypesForObject($object); 32 + $types = array_fuse($types); 33 + 34 + $fields = $this->buildCustomEditFields($object); 35 + 36 + if ($object instanceof PhabricatorPolicyInterface) { 37 + $policies = id(new PhabricatorPolicyQuery()) 38 + ->setViewer($viewer) 39 + ->setObject($object) 40 + ->execute(); 41 + 42 + $map = array( 43 + PhabricatorTransactions::TYPE_VIEW_POLICY => array( 44 + 'key' => 'policy.view', 45 + 'aliases' => array('view'), 46 + 'capability' => PhabricatorPolicyCapability::CAN_VIEW, 47 + ), 48 + PhabricatorTransactions::TYPE_EDIT_POLICY => array( 49 + 'key' => 'policy.edit', 50 + 'aliases' => array('edit'), 51 + 'capability' => PhabricatorPolicyCapability::CAN_EDIT, 52 + ), 53 + PhabricatorTransactions::TYPE_JOIN_POLICY => array( 54 + 'key' => 'policy.join', 55 + 'aliases' => array('join'), 56 + 'capability' => PhabricatorPolicyCapability::CAN_JOIN, 57 + ), 58 + ); 59 + 60 + foreach ($map as $type => $spec) { 61 + if (empty($types[$type])) { 62 + continue; 63 + } 64 + 65 + $capability = $spec['capability']; 66 + $key = $spec['key']; 67 + $aliases = $spec['aliases']; 68 + 69 + $policy_field = id(new PhabricatorPolicyEditField()) 70 + ->setKey($key) 71 + ->setAliases($aliases) 72 + ->setCapability($capability) 73 + ->setPolicies($policies) 74 + ->setTransactionType($type) 75 + ->setValue($object->getPolicy($capability)); 76 + $fields[] = $policy_field; 77 + 78 + if ($object instanceof PhabricatorSpacesInterface) { 79 + if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { 80 + $type_space = PhabricatorTransactions::TYPE_SPACE; 81 + if (isset($types[$type_space])) { 82 + $space_field = id(new PhabricatorSpaceEditField()) 83 + ->setKey('spacePHID') 84 + ->setAliases(array('space', 'policy.space')) 85 + ->setTransactionType($type_space) 86 + ->setValue($object->getSpacePHID()); 87 + $fields[] = $space_field; 88 + 89 + $policy_field->setSpaceField($space_field); 90 + } 91 + } 92 + } 93 + } 94 + } 95 + 96 + $edge_type = PhabricatorTransactions::TYPE_EDGE; 97 + $object_phid = $object->getPHID(); 98 + 99 + $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; 100 + 101 + if ($object instanceof PhabricatorProjectInterface) { 102 + if (isset($types[$edge_type])) { 103 + if ($object_phid) { 104 + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( 105 + $object_phid, 106 + $project_edge_type); 107 + $project_phids = array_reverse($project_phids); 108 + } else { 109 + $project_phids = array(); 110 + } 111 + 112 + $edge_field = id(new PhabricatorDatasourceEditField()) 113 + ->setKey('projectPHIDs') 114 + ->setLabel(pht('Projects')) 115 + ->setDatasource(new PhabricatorProjectDatasource()) 116 + ->setAliases(array('project', 'projects')) 117 + ->setTransactionType($edge_type) 118 + ->setMetadataValue('edge:type', $project_edge_type) 119 + ->setValue($project_phids); 120 + $fields[] = $edge_field; 121 + } 122 + } 123 + 124 + $subscribers_type = PhabricatorTransactions::TYPE_SUBSCRIBERS; 125 + 126 + if ($object instanceof PhabricatorSubscribableInterface) { 127 + if (isset($types[$subscribers_type])) { 128 + if ($object_phid) { 129 + $sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( 130 + $object_phid); 131 + } else { 132 + // TODO: Allow applications to provide default subscribers; Maniphest 133 + // does this at a minimum. 134 + $sub_phids = array(); 135 + } 136 + 137 + $subscribers_field = id(new PhabricatorDatasourceEditField()) 138 + ->setKey('subscriberPHIDs') 139 + ->setLabel(pht('Subscribers')) 140 + ->setDatasource(new PhabricatorMetaMTAMailableDatasource()) 141 + ->setAliases(array('subscriber', 'subscribers')) 142 + ->setTransactionType($subscribers_type) 143 + ->setValue($sub_phids); 144 + $fields[] = $subscribers_field; 145 + } 146 + } 147 + 148 + return $fields; 149 + } 150 + 151 + abstract protected function newEditableObject(); 152 + abstract protected function newObjectQuery(); 153 + abstract protected function buildCustomEditFields($object); 154 + 155 + abstract protected function getObjectCreateTitleText($object); 156 + abstract protected function getObjectEditTitleText($object); 157 + abstract protected function getObjectCreateShortText($object); 158 + abstract protected function getObjectEditShortText($object); 159 + abstract protected function getObjectViewURI($object); 160 + 161 + protected function getObjectCreateCancelURI($object) { 162 + return $this->getController()->getApplicationURI(); 163 + } 164 + 165 + protected function getObjectEditCancelURI($object) { 166 + return $this->getObjectViewURI($object); 167 + } 168 + 169 + protected function getObjectCreateButtonText($object) { 170 + return $this->getObjectCreateTitleText($object); 171 + } 172 + 173 + protected function getObjectEditButtonText($object) { 174 + return pht('Save Changes'); 175 + } 176 + 177 + final public function buildResponse() { 178 + $controller = $this->getController(); 179 + $viewer = $this->getViewer(); 180 + $request = $controller->getRequest(); 181 + 182 + $id = $request->getURIData('id'); 183 + if ($id) { 184 + $object = $this->newObjectQuery() 185 + ->setViewer($viewer) 186 + ->withIDs(array($id)) 187 + ->requireCapabilities( 188 + array( 189 + PhabricatorPolicyCapability::CAN_VIEW, 190 + PhabricatorPolicyCapability::CAN_EDIT, 191 + )) 192 + ->executeOne(); 193 + if (!$object) { 194 + return new Aphront404Response(); 195 + } 196 + 197 + $is_create = false; 198 + } else { 199 + $object = $this->newEditableObject(); 200 + 201 + $is_create = true; 202 + } 203 + 204 + $fields = $this->buildEditFields($object); 205 + 206 + foreach ($fields as $field) { 207 + $field 208 + ->setViewer($viewer) 209 + ->setObject($object); 210 + } 211 + 212 + $validation_exception = null; 213 + if ($request->isFormPost()) { 214 + foreach ($fields as $field) { 215 + $field->readValueFromSubmit($request); 216 + } 217 + 218 + $template = $object->getApplicationTransactionTemplate(); 219 + 220 + $xactions = array(); 221 + foreach ($fields as $field) { 222 + $xactions[] = $field->generateTransaction(clone $template); 223 + } 224 + 225 + $editor = $object->getApplicationTransactionEditor() 226 + ->setActor($viewer) 227 + ->setContentSourceFromRequest($request) 228 + ->setContinueOnNoEffect(true) 229 + ->setContinueOnMissingFields(false); 230 + 231 + try { 232 + 233 + $editor->applyTransactions($object, $xactions); 234 + 235 + return id(new AphrontRedirectResponse()) 236 + ->setURI($this->getObjectViewURI($object)); 237 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 238 + $validation_exception = $ex; 239 + } 240 + } else { 241 + if ($is_create) { 242 + foreach ($fields as $field) { 243 + $field->readValueFromRequest($request); 244 + } 245 + } else { 246 + foreach ($fields as $field) { 247 + $field->readValueFromObject($object); 248 + } 249 + } 250 + } 251 + 252 + $box = id(new PHUIObjectBoxView()) 253 + ->setUser($viewer); 254 + 255 + $crumbs = $controller->buildApplicationCrumbsForEditEngine(); 256 + 257 + if ($is_create) { 258 + $header_text = $this->getObjectCreateTitleText($object); 259 + 260 + $crumbs->addTextCrumb( 261 + $this->getObjectCreateShortText($object)); 262 + 263 + $cancel_uri = $this->getObjectCreateCancelURI($object); 264 + $submit_button = $this->getObjectCreateButtonText($object); 265 + } else { 266 + $header_text = $this->getObjectEditTitleText($object); 267 + 268 + $crumbs->addTextCrumb( 269 + $this->getObjectEditShortText($object), 270 + $this->getObjectViewURI($object)); 271 + 272 + $cancel_uri = $this->getObjectEditCancelURI($object); 273 + $submit_button = $this->getObjectEditButtonText($object); 274 + } 275 + 276 + $box->setHeaderText($header_text); 277 + 278 + $form = id(new AphrontFormView()) 279 + ->setUser($viewer); 280 + 281 + foreach ($fields as $field) { 282 + $field->appendToForm($form); 283 + } 284 + 285 + $form->appendControl( 286 + id(new AphrontFormSubmitControl()) 287 + ->addCancelButton($cancel_uri) 288 + ->setValue($submit_button)); 289 + 290 + $box->appendChild($form); 291 + 292 + if ($validation_exception) { 293 + $box->setValidationException($validation_exception); 294 + } 295 + 296 + return $controller->newPage() 297 + ->setTitle($header_text) 298 + ->setCrumbs($crumbs) 299 + ->appendChild($box); 300 + } 301 + 302 + 303 + }
+21
src/applications/transactions/editfield/PhabricatorDatasourceEditField.php
··· 1 + <?php 2 + 3 + final class PhabricatorDatasourceEditField 4 + extends PhabricatorTokenizerEditField { 5 + 6 + private $datasource; 7 + 8 + public function setDatasource(PhabricatorTypeaheadDatasource $datasource) { 9 + $this->datasource = $datasource; 10 + return $this; 11 + } 12 + 13 + public function getDatasource() { 14 + return $this->datasource; 15 + } 16 + 17 + protected function newDatasource() { 18 + return id(clone $this->getDatasource()); 19 + } 20 + 21 + }
+212
src/applications/transactions/editfield/PhabricatorEditField.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorEditField extends Phobject { 4 + 5 + private $key; 6 + private $viewer; 7 + private $label; 8 + private $aliases = array(); 9 + private $value; 10 + private $hasValue = false; 11 + private $object; 12 + private $transactionType; 13 + private $metadata = array(); 14 + 15 + public function setKey($key) { 16 + $this->key = $key; 17 + return $this; 18 + } 19 + 20 + public function getKey() { 21 + return $this->key; 22 + } 23 + 24 + public function setLabel($label) { 25 + $this->label = $label; 26 + return $this; 27 + } 28 + 29 + public function getLabel() { 30 + return $this->label; 31 + } 32 + 33 + public function setViewer(PhabricatorUser $viewer) { 34 + $this->viewer = $viewer; 35 + return $this; 36 + } 37 + 38 + public function getViewer() { 39 + return $this->viewer; 40 + } 41 + 42 + public function setAliases(array $aliases) { 43 + $this->aliases = $aliases; 44 + return $this; 45 + } 46 + 47 + public function getAliases() { 48 + return $this->aliases; 49 + } 50 + 51 + public function setObject($object) { 52 + $this->object = $object; 53 + return $this; 54 + } 55 + 56 + public function getObject() { 57 + return $this->object; 58 + } 59 + 60 + abstract protected function newControl(); 61 + 62 + protected function renderControl() { 63 + $control = $this->newControl(); 64 + if ($control === null) { 65 + return null; 66 + } 67 + 68 + $control 69 + ->setValue($this->getValueForControl()) 70 + ->setName($this->getKey()); 71 + 72 + if (!$control->getLabel()) { 73 + $control->setLabel($this->getLabel()); 74 + } 75 + 76 + return $control; 77 + } 78 + 79 + public function appendToForm(AphrontFormView $form) { 80 + $control = $this->renderControl(); 81 + if ($control !== null) { 82 + $form->appendControl($control); 83 + } 84 + return $this; 85 + } 86 + 87 + protected function getValueForControl() { 88 + return $this->getValue(); 89 + } 90 + 91 + protected function getValue() { 92 + return $this->value; 93 + } 94 + 95 + public function setValue($value) { 96 + $this->hasValue = true; 97 + $this->value = $value; 98 + return $this; 99 + } 100 + 101 + public function generateTransaction( 102 + PhabricatorApplicationTransaction $xaction) { 103 + 104 + $xaction 105 + ->setTransactionType($this->getTransactionType()) 106 + ->setNewValue($this->getValueForTransaction()); 107 + 108 + foreach ($this->metadata as $key => $value) { 109 + $xaction->setMetadataValue($key, $value); 110 + } 111 + 112 + return $xaction; 113 + } 114 + 115 + public function setMetadataValue($key, $value) { 116 + $this->metadata[$key] = $value; 117 + return $this; 118 + } 119 + 120 + protected function getValueForTransaction() { 121 + return $this->getValue(); 122 + } 123 + 124 + public function getTransactionType() { 125 + if (!$this->transactionType) { 126 + throw new PhutilInvalidStateException('setTransactionType'); 127 + } 128 + return $this->transactionType; 129 + } 130 + 131 + public function setTransactionType($type) { 132 + $this->transactionType = $type; 133 + return $this; 134 + } 135 + 136 + public function readValueFromRequest(AphrontRequest $request) { 137 + $check = array_merge(array($this->getKey()), $this->getAliases()); 138 + foreach ($check as $key) { 139 + if (!$this->getValueExistsInRequest($request, $key)) { 140 + continue; 141 + } 142 + 143 + $this->value = $this->getValueFromRequest($request, $key); 144 + return; 145 + } 146 + 147 + $this->readValueFromObject($this->getObject()); 148 + 149 + return $this; 150 + } 151 + 152 + public function readValueFromObject($object) { 153 + $this->value = $this->getValueFromObject($object); 154 + return $this; 155 + } 156 + 157 + protected function getValueFromObject($object) { 158 + if ($this->hasValue) { 159 + return $this->value; 160 + } else { 161 + return $this->getDefaultValue(); 162 + } 163 + } 164 + 165 + protected function getValueExistsInRequest(AphrontRequest $request, $key) { 166 + return $this->getValueExistsInSubmit($request, $key); 167 + } 168 + 169 + protected function getValueFromRequest(AphrontRequest $request, $key) { 170 + return $this->getValueFromSubmit($request, $key); 171 + } 172 + 173 + public function readValueFromSubmit(AphrontRequest $request) { 174 + $key = $this->getKey(); 175 + if ($this->getValueExistsInSubmit($request, $key)) { 176 + $value = $this->getValueFromSubmit($request, $key); 177 + } else { 178 + $value = $this->getDefaultValue(); 179 + } 180 + $this->value = $value; 181 + return $this; 182 + } 183 + 184 + protected function getValueExistsInSubmit(AphrontRequest $request, $key) { 185 + return $request->getExists($key); 186 + } 187 + 188 + protected function getValueFromSubmit(AphrontRequest $request, $key) { 189 + return $request->getStr($key); 190 + } 191 + 192 + protected function getDefaultValue() { 193 + return null; 194 + } 195 + 196 + protected function getListFromRequest( 197 + AphrontRequest $request, 198 + $key) { 199 + 200 + $list = $request->getArr($key, null); 201 + if ($list === null) { 202 + $list = $request->getStrList($key); 203 + } 204 + 205 + if (!$list) { 206 + return array(); 207 + } 208 + 209 + return $list; 210 + } 211 + 212 + }
+54
src/applications/transactions/editfield/PhabricatorPolicyEditField.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyEditField 4 + extends PhabricatorEditField { 5 + 6 + private $policies; 7 + private $capability; 8 + private $spaceField; 9 + 10 + public function setPolicies(array $policies) { 11 + $this->policies = $policies; 12 + return $this; 13 + } 14 + 15 + public function getPolicies() { 16 + if ($this->policies === null) { 17 + throw new PhutilInvalidStateException('setPolicies'); 18 + } 19 + return $this->policies; 20 + } 21 + 22 + public function setCapability($capability) { 23 + $this->capability = $capability; 24 + return $this; 25 + } 26 + 27 + public function getCapability() { 28 + return $this->capability; 29 + } 30 + 31 + public function setSpaceField(PhabricatorSpaceEditField $space_field) { 32 + $this->spaceField = $space_field; 33 + return $this; 34 + } 35 + 36 + public function getSpaceField() { 37 + return $this->spaceField; 38 + } 39 + 40 + protected function newControl() { 41 + $control = id(new AphrontFormPolicyControl()) 42 + ->setCapability($this->getCapability()) 43 + ->setPolicyObject($this->getObject()) 44 + ->setPolicies($this->getPolicies()); 45 + 46 + $space_field = $this->getSpaceField(); 47 + if ($space_field) { 48 + $control->setSpacePHID($space_field->getValueForControl()); 49 + } 50 + 51 + return $control; 52 + } 53 + 54 + }
+25
src/applications/transactions/editfield/PhabricatorSelectEditField.php
··· 1 + <?php 2 + 3 + final class PhabricatorSelectEditField 4 + extends PhabricatorEditField { 5 + 6 + private $options; 7 + 8 + public function setOptions(array $options) { 9 + $this->options = $options; 10 + return $this; 11 + } 12 + 13 + public function getOptions() { 14 + if ($this->options === null) { 15 + throw new PhutilInvalidStateException('setOptions'); 16 + } 17 + return $this->options; 18 + } 19 + 20 + protected function newControl() { 21 + return id(new AphrontFormSelectControl()) 22 + ->setOptions($this->getOptions()); 23 + } 24 + 25 + }
+12
src/applications/transactions/editfield/PhabricatorSpaceEditField.php
··· 1 + <?php 2 + 3 + final class PhabricatorSpaceEditField 4 + extends PhabricatorEditField { 5 + 6 + protected function newControl() { 7 + // NOTE: This field doesn't do anything on its own, it just serves as a 8 + // companion to the associated View Policy field. 9 + return null; 10 + } 11 + 12 + }
+42
src/applications/transactions/editfield/PhabricatorTextAreaEditField.php
··· 1 + <?php 2 + 3 + final class PhabricatorTextAreaEditField 4 + extends PhabricatorEditField { 5 + 6 + private $monospaced; 7 + private $height; 8 + 9 + public function setMonospaced($monospaced) { 10 + $this->monospaced = $monospaced; 11 + return $this; 12 + } 13 + 14 + public function getMonospaced() { 15 + return $this->monospaced; 16 + } 17 + 18 + public function setHeight($height) { 19 + $this->height = $height; 20 + return $this; 21 + } 22 + 23 + public function getHeight() { 24 + return $this->height; 25 + } 26 + 27 + protected function newControl() { 28 + $control = new AphrontFormTextAreaControl(); 29 + 30 + if ($this->getMonospaced()) { 31 + $control->setCustomClass('PhabricatorMonospaced'); 32 + } 33 + 34 + $height = $this->getHeight(); 35 + if ($height) { 36 + $control->setHeight($height); 37 + } 38 + 39 + return $control; 40 + } 41 + 42 + }
+10
src/applications/transactions/editfield/PhabricatorTextEditField.php
··· 1 + <?php 2 + 3 + final class PhabricatorTextEditField 4 + extends PhabricatorEditField { 5 + 6 + protected function newControl() { 7 + return new AphrontFormTextControl(); 8 + } 9 + 10 + }
+90
src/applications/transactions/editfield/PhabricatorTokenizerEditField.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorTokenizerEditField 4 + extends PhabricatorEditField { 5 + 6 + private $originalValue; 7 + 8 + abstract protected function newDatasource(); 9 + 10 + protected function newControl() { 11 + $control = id(new AphrontFormTokenizerControl()) 12 + ->setDatasource($this->newDatasource()); 13 + 14 + if ($this->originalValue !== null) { 15 + $control->setOriginalValue($this->originalValue); 16 + } 17 + 18 + return $control; 19 + } 20 + 21 + public function setOriginalValue(array $value) { 22 + $this->originalValue = $value; 23 + return $this; 24 + } 25 + 26 + public function setValue($value) { 27 + $this->originalValue = $value; 28 + return parent::setValue($value); 29 + } 30 + 31 + protected function getValueFromSubmit(AphrontRequest $request, $key) { 32 + // TODO: Maybe move this unusual read somewhere else so subclassing this 33 + // correctly is easier? 34 + $this->originalValue = $request->getArr($key.'.original'); 35 + 36 + return $this->getListFromRequest($request, $key); 37 + } 38 + 39 + protected function getDefaultValue() { 40 + return array(); 41 + } 42 + 43 + protected function getValueForTransaction() { 44 + $new = parent::getValueForTransaction(); 45 + 46 + $edge_types = array( 47 + PhabricatorTransactions::TYPE_EDGE => true, 48 + PhabricatorTransactions::TYPE_SUBSCRIBERS => true, 49 + ); 50 + 51 + if (isset($edge_types[$this->getTransactionType()])) { 52 + if ($this->originalValue !== null) { 53 + // If we're building an edge transaction and the request has data 54 + // about the original value the user saw when they loaded the form, 55 + // interpret the edit as a mixture of "+" and "-" operations instead 56 + // of a single "=" operation. This limits our exposure to race 57 + // conditions by making most concurrent edits merge correctly. 58 + 59 + $new = parent::getValueForTransaction(); 60 + $old = $this->originalValue; 61 + 62 + $add = array_diff($new, $old); 63 + $rem = array_diff($old, $new); 64 + 65 + $value = array(); 66 + 67 + if ($add) { 68 + $value['+'] = array_fuse($add); 69 + } 70 + if ($rem) { 71 + $value['-'] = array_fuse($rem); 72 + } 73 + 74 + return $value; 75 + } else { 76 + 77 + if (!is_array($new)) { 78 + throw new Exception(print_r($new, true)); 79 + } 80 + 81 + return array( 82 + '=' => array_fuse($new), 83 + ); 84 + } 85 + } 86 + 87 + return $new; 88 + } 89 + 90 + }
+14 -1
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 242 242 return $this->applicationEmail; 243 243 } 244 244 245 + public function getTransactionTypesForObject($object) { 246 + $old = $this->object; 247 + try { 248 + $this->object = $object; 249 + $result = $this->getTransactionTypes(); 250 + $this->object = $old; 251 + } catch (Exception $ex) { 252 + $this->object = $old; 253 + throw $ex; 254 + } 255 + return $result; 256 + } 257 + 245 258 public function getTransactionTypes() { 246 259 $types = array(); 247 260 ··· 1650 1663 throw new Exception( 1651 1664 pht( 1652 1665 "Invalid '%s' value for PHID transaction. Value should contain only ". 1653 - "keys '%s' (add PHIDs), '%' (remove PHIDs) and '%s' (set PHIDS).", 1666 + "keys '%s' (add PHIDs), '%s' (remove PHIDs) and '%s' (set PHIDS).", 1654 1667 'new', 1655 1668 '+', 1656 1669 '-',
+25
src/view/control/AphrontTokenizerTemplateView.php
··· 6 6 private $name; 7 7 private $id; 8 8 private $browseURI; 9 + private $originalValue; 9 10 10 11 public function setBrowseURI($browse_uri) { 11 12 $this->browseURI = $browse_uri; ··· 34 35 35 36 public function getName() { 36 37 return $this->name; 38 + } 39 + 40 + public function setOriginalValue(array $original_value) { 41 + $this->originalValue = $original_value; 42 + return $this; 43 + } 44 + 45 + public function getOriginalValue() { 46 + return $this->originalValue; 37 47 } 38 48 39 49 public function render() { ··· 85 95 $classes[] = 'has-browse'; 86 96 } 87 97 98 + $original = array(); 99 + $original_value = $this->getOriginalValue(); 100 + if ($original_value) { 101 + foreach ($this->getOriginalValue() as $value) { 102 + $original[] = phutil_tag( 103 + 'input', 104 + array( 105 + 'type' => 'hidden', 106 + 'name' => $name.'.original[]', 107 + 'value' => $value, 108 + )); 109 + } 110 + } 111 + 88 112 $frame = javelin_tag( 89 113 'div', 90 114 array( ··· 94 118 array( 95 119 $container, 96 120 $browse, 121 + $original, 97 122 )); 98 123 99 124 return $frame;
+19 -4
src/view/form/control/AphrontFormTokenizerControl.php
··· 7 7 private $limit; 8 8 private $placeholder; 9 9 private $handles; 10 + private $originalValue; 10 11 11 12 public function setDatasource(PhabricatorTypeaheadDatasource $datasource) { 12 13 $this->datasource = $datasource; ··· 30 31 public function setPlaceholder($placeholder) { 31 32 $this->placeholder = $placeholder; 32 33 return $this; 34 + } 35 + 36 + public function setOriginalValue(array $original_value) { 37 + $this->originalValue = $original_value; 38 + return $this; 39 + } 40 + 41 + public function getOriginalValue() { 42 + return $this->originalValue; 33 43 } 34 44 35 45 public function willRender() { ··· 69 79 $token->setInputName($this->getName()); 70 80 } 71 81 72 - $template = new AphrontTokenizerTemplateView(); 73 - $template->setName($name); 74 - $template->setID($id); 75 - $template->setValue($tokens); 82 + $template = id(new AphrontTokenizerTemplateView()) 83 + ->setName($name) 84 + ->setID($id) 85 + ->setValue($tokens); 86 + 87 + $original_value = $this->getOriginalValue(); 88 + if ($original_value !== null) { 89 + $template->setOriginalValue($original_value); 90 + } 76 91 77 92 $username = null; 78 93 if ($this->user) {