@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.

Improve code structure of PHID fields in EditEngine

Summary: Ref T9132. I had some hacks in place for dealing with Edge/Subscribers stuff. Clean that up so it's structured a little better.

Test Plan:
- Edited subscribers and projects.
- Verified things still show up in Conduit.
- Made concurrent edits (added a project in one window, removed it in another window, got a clean result with a correct merge of the two edits).

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9132

Differential Revision: https://secure.phabricator.com/D14601

+207 -126
+9 -5
src/__phutil_library_map__.php
··· 145 145 'AphrontJavelinView' => 'view/AphrontJavelinView.php', 146 146 'AphrontKeyboardShortcutsAvailableView' => 'view/widget/AphrontKeyboardShortcutsAvailableView.php', 147 147 'AphrontListFilterView' => 'view/layout/AphrontListFilterView.php', 148 + 'AphrontListHTTPParameterType' => 'aphront/httpparametertype/AphrontListHTTPParameterType.php', 148 149 'AphrontMalformedRequestException' => 'aphront/exception/AphrontMalformedRequestException.php', 149 150 'AphrontMoreView' => 'view/layout/AphrontMoreView.php', 150 151 'AphrontMultiColumnView' => 'view/layout/AphrontMultiColumnView.php', ··· 2580 2581 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 2581 2582 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', 2582 2583 'PhabricatorPHIDInterface' => 'applications/phid/interface/PhabricatorPHIDInterface.php', 2584 + 'PhabricatorPHIDListEditField' => 'applications/transactions/editfield/PhabricatorPHIDListEditField.php', 2583 2585 'PhabricatorPHIDResolver' => 'applications/phid/resolver/PhabricatorPHIDResolver.php', 2584 2586 'PhabricatorPHIDType' => 'applications/phid/type/PhabricatorPHIDType.php', 2585 2587 'PhabricatorPHIDTypeTestCase' => 'applications/phid/type/__tests__/PhabricatorPHIDTypeTestCase.php', ··· 3943 3945 'AphrontJavelinView' => 'AphrontView', 3944 3946 'AphrontKeyboardShortcutsAvailableView' => 'AphrontView', 3945 3947 'AphrontListFilterView' => 'AphrontView', 3948 + 'AphrontListHTTPParameterType' => 'AphrontHTTPParameterType', 3946 3949 'AphrontMalformedRequestException' => 'AphrontException', 3947 3950 'AphrontMoreView' => 'AphrontView', 3948 3951 'AphrontMultiColumnView' => 'AphrontView', 3949 3952 'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase', 3950 3953 'AphrontNullView' => 'AphrontView', 3951 3954 'AphrontPHIDHTTPParameterType' => 'AphrontHTTPParameterType', 3952 - 'AphrontPHIDListHTTPParameterType' => 'AphrontHTTPParameterType', 3955 + 'AphrontPHIDListHTTPParameterType' => 'AphrontListHTTPParameterType', 3953 3956 'AphrontPHPHTTPSink' => 'AphrontHTTPSink', 3954 3957 'AphrontPageView' => 'AphrontView', 3955 3958 'AphrontPlainTextResponse' => 'AphrontResponse', 3956 3959 'AphrontProgressBarView' => 'AphrontBarView', 3957 - 'AphrontProjectListHTTPParameterType' => 'AphrontHTTPParameterType', 3960 + 'AphrontProjectListHTTPParameterType' => 'AphrontListHTTPParameterType', 3958 3961 'AphrontProxyResponse' => array( 3959 3962 'AphrontResponse', 3960 3963 'AphrontResponseProducerInterface', ··· 3974 3977 'AphrontStackTraceView' => 'AphrontView', 3975 3978 'AphrontStandaloneHTMLResponse' => 'AphrontHTMLResponse', 3976 3979 'AphrontStringHTTPParameterType' => 'AphrontHTTPParameterType', 3977 - 'AphrontStringListHTTPParameterType' => 'AphrontHTTPParameterType', 3980 + 'AphrontStringListHTTPParameterType' => 'AphrontListHTTPParameterType', 3978 3981 'AphrontTableView' => 'AphrontView', 3979 3982 'AphrontTagView' => 'AphrontView', 3980 3983 'AphrontTokenizerTemplateView' => 'AphrontView', 3981 3984 'AphrontTypeaheadTemplateView' => 'AphrontView', 3982 3985 'AphrontUnhandledExceptionResponse' => 'AphrontStandaloneHTMLResponse', 3983 - 'AphrontUserListHTTPParameterType' => 'AphrontHTTPParameterType', 3986 + 'AphrontUserListHTTPParameterType' => 'AphrontListHTTPParameterType', 3984 3987 'AphrontView' => array( 3985 3988 'Phobject', 3986 3989 'PhutilSafeHTMLProducerInterface', ··· 6752 6755 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', 6753 6756 'PhabricatorPHID' => 'Phobject', 6754 6757 'PhabricatorPHIDConstants' => 'Phobject', 6758 + 'PhabricatorPHIDListEditField' => 'PhabricatorEditField', 6755 6759 'PhabricatorPHIDResolver' => 'Phobject', 6756 6760 'PhabricatorPHIDType' => 'Phobject', 6757 6761 'PhabricatorPHIDTypeTestCase' => 'PhutilTestCase', ··· 7437 7441 'PhabricatorTokenReceiverQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 7438 7442 'PhabricatorTokenTokenPHIDType' => 'PhabricatorPHIDType', 7439 7443 'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener', 7440 - 'PhabricatorTokenizerEditField' => 'PhabricatorEditField', 7444 + 'PhabricatorTokenizerEditField' => 'PhabricatorPHIDListEditField', 7441 7445 'PhabricatorTokensApplication' => 'PhabricatorApplication', 7442 7446 'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel', 7443 7447 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample',
+10
src/aphront/httpparametertype/AphrontListHTTPParameterType.php
··· 1 + <?php 2 + 3 + abstract class AphrontListHTTPParameterType 4 + extends AphrontHTTPParameterType { 5 + 6 + protected function getParameterDefault() { 7 + return array(); 8 + } 9 + 10 + }
+1 -1
src/aphront/httpparametertype/AphrontPHIDListHTTPParameterType.php
··· 1 1 <?php 2 2 3 3 final class AphrontPHIDListHTTPParameterType 4 - extends AphrontHTTPParameterType { 4 + extends AphrontListHTTPParameterType { 5 5 6 6 protected function getParameterValue(AphrontRequest $request, $key) { 7 7 $type = new AphrontStringListHTTPParameterType();
+1 -1
src/aphront/httpparametertype/AphrontProjectListHTTPParameterType.php
··· 1 1 <?php 2 2 3 3 final class AphrontProjectListHTTPParameterType 4 - extends AphrontHTTPParameterType { 4 + extends AphrontListHTTPParameterType { 5 5 6 6 protected function getParameterValue(AphrontRequest $request, $key) { 7 7 $type = new AphrontStringListHTTPParameterType();
+1 -1
src/aphront/httpparametertype/AphrontStringListHTTPParameterType.php
··· 1 1 <?php 2 2 3 3 final class AphrontStringListHTTPParameterType 4 - extends AphrontHTTPParameterType { 4 + extends AphrontListHTTPParameterType { 5 5 6 6 protected function getParameterValue(AphrontRequest $request, $key) { 7 7 $list = $request->getArr($key, null);
+1 -1
src/aphront/httpparametertype/AphrontUserListHTTPParameterType.php
··· 1 1 <?php 2 2 3 3 final class AphrontUserListHTTPParameterType 4 - extends AphrontHTTPParameterType { 4 + extends AphrontListHTTPParameterType { 5 5 6 6 protected function getParameterValue(AphrontRequest $request, $key) { 7 7 $type = new AphrontStringListHTTPParameterType();
+5
src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php
··· 48 48 ->setEditTypeKey('projects') 49 49 ->setDescription(pht('Add or remove associated projects.')) 50 50 ->setAliases(array('project', 'projects')) 51 + ->setUseEdgeTransactions(true) 52 + ->setEdgeTransactionDescriptions( 53 + pht('Add projects.'), 54 + pht('Remove projects.'), 55 + pht('Set associated projects, overwriting current value.')) 51 56 ->setTransactionType($edge_type) 52 57 ->setMetadataValue('edge:type', $project_edge_type) 53 58 ->setValue($project_phids);
+5
src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php
··· 45 45 ->setEditTypeKey('subscribers') 46 46 ->setDescription(pht('Manage subscribers.')) 47 47 ->setAliases(array('subscriber', 'subscribers')) 48 + ->setUseEdgeTransactions(true) 49 + ->setEdgeTransactionDescriptions( 50 + pht('Add subscribers.'), 51 + pht('Remove subscribers.'), 52 + pht('Set subscribers, overwriting current value.')) 48 53 ->setTransactionType($subscribers_type) 49 54 ->setValue($sub_phids); 50 55
+5
src/applications/transactions/editengine/PhabricatorEditEngine.php
··· 1048 1048 $types = array(); 1049 1049 foreach ($fields as $field) { 1050 1050 $field_types = $field->getEditTransactionTypes(); 1051 + 1052 + if ($field_types === null) { 1053 + continue; 1054 + } 1055 + 1051 1056 foreach ($field_types as $field_type) { 1052 1057 $field_type->setField($field); 1053 1058 $types[$field_type->getEditType()] = $field_type;
+49 -50
src/applications/transactions/editfield/PhabricatorEditField.php
··· 7 7 private $label; 8 8 private $aliases = array(); 9 9 private $value; 10 + private $initialValue; 10 11 private $hasValue = false; 11 12 private $object; 12 13 private $transactionType; ··· 262 263 263 264 public function setValue($value) { 264 265 $this->hasValue = true; 266 + $this->initialValue = $value; 265 267 $this->value = $value; 266 268 return $this; 267 269 } ··· 287 289 public function setMetadataValue($key, $value) { 288 290 $this->metadata[$key] = $value; 289 291 return $this; 292 + } 293 + 294 + public function getMetadata() { 295 + return $this->metadata; 290 296 } 291 297 292 298 protected function getValueForTransaction() { ··· 348 354 return $this->getValueFromSubmit($request, $key); 349 355 } 350 356 357 + 358 + /** 359 + * Read and return the value the object had when the user first loaded the 360 + * form. 361 + * 362 + * This is the initial value from the user's point of view when they started 363 + * the edit process, and used primarily to prevent race conditions for fields 364 + * like "Projects" and "Subscribers" that use tokenizers and support edge 365 + * transactions. 366 + * 367 + * Most fields do not need to store these values or deal with initial value 368 + * handling. 369 + * 370 + * @param AphrontRequest Request to read from. 371 + * @param string Key to read. 372 + * @return wild Value read from request. 373 + */ 374 + protected function getInitialValueFromSubmit(AphrontRequest $request, $key) { 375 + return null; 376 + } 377 + 378 + public function getInitialValue() { 379 + return $this->initialValue; 380 + } 381 + 351 382 public function readValueFromSubmit(AphrontRequest $request) { 352 383 $key = $this->getKey(); 353 384 if ($this->getValueExistsInSubmit($request, $key)) { ··· 356 387 $value = $this->getDefaultValue(); 357 388 } 358 389 $this->value = $value; 390 + 391 + $initial_value = $this->getInitialValueFromSubmit($request, $key); 392 + $this->initialValue = $initial_value; 393 + 359 394 return $this; 360 395 } 361 396 ··· 414 449 ->setValueType($this->getHTTPParameterType()->getTypeName()); 415 450 } 416 451 417 - public function getEditTransactionTypes() { 452 + protected function getEditTransactionType() { 418 453 $transaction_type = $this->getTransactionType(); 454 + 419 455 if ($transaction_type === null) { 420 - return array(); 456 + return null; 421 457 } 422 458 423 459 $type_key = $this->getEditTypeKey(); 424 460 425 - // TODO: This is a pretty big pile of hard-coded hacks for now. 426 - 427 - $edge_types = array( 428 - PhabricatorTransactions::TYPE_EDGE => array( 429 - '+' => pht('Add projects.'), 430 - '-' => pht('Remove projects.'), 431 - '=' => pht('Set associated projects, overwriting current value.'), 432 - ), 433 - PhabricatorTransactions::TYPE_SUBSCRIBERS => array( 434 - '+' => pht('Add subscribers.'), 435 - '-' => pht('Remove subscribers.'), 436 - '=' => pht('Set subscribers, overwriting current value.'), 437 - ), 438 - ); 461 + return $this->newEditType() 462 + ->setEditType($type_key) 463 + ->setTransactionType($transaction_type) 464 + ->setDescription($this->getDescription()) 465 + ->setMetadata($this->getMetadata()); 466 + } 439 467 440 - if (isset($edge_types[$transaction_type])) { 441 - $base = id(new PhabricatorEdgeEditType()) 442 - ->setTransactionType($transaction_type) 443 - ->setMetadata($this->metadata); 468 + public function getEditTransactionTypes() { 469 + $edit_type = $this->getEditTransactionType(); 444 470 445 - $strings = $edge_types[$transaction_type]; 446 - 447 - $add = id(clone $base) 448 - ->setEditType($type_key.'.add') 449 - ->setEdgeOperation('+') 450 - ->setDescription($strings['+']) 451 - ->setValueDescription(pht('List of PHIDs to add.')); 452 - $rem = id(clone $base) 453 - ->setEditType($type_key.'.remove') 454 - ->setEdgeOperation('-') 455 - ->setDescription($strings['-']) 456 - ->setValueDescription(pht('List of PHIDs to remove.')); 457 - $set = id(clone $base) 458 - ->setEditType($type_key.'.set') 459 - ->setEdgeOperation('=') 460 - ->setDescription($strings['=']) 461 - ->setValueDescription(pht('List of PHIDs to set.')); 462 - 463 - return array( 464 - $add, 465 - $rem, 466 - $set, 467 - ); 471 + if ($edit_type === null) { 472 + return null; 468 473 } 469 474 470 - return array( 471 - $this->newEditType() 472 - ->setEditType($type_key) 473 - ->setTransactionType($transaction_type) 474 - ->setDescription($this->getDescription()) 475 - ->setMetadata($this->metadata), 476 - ); 475 + return array($edit_type); 477 476 } 478 477 479 478 }
+114
src/applications/transactions/editfield/PhabricatorPHIDListEditField.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorPHIDListEditField 4 + extends PhabricatorEditField { 5 + 6 + private $useEdgeTransactions; 7 + private $transactionDescriptions = array(); 8 + 9 + public function setUseEdgeTransactions($use_edge_transactions) { 10 + $this->useEdgeTransactions = $use_edge_transactions; 11 + return $this; 12 + } 13 + 14 + public function getUseEdgeTransactions() { 15 + return $this->useEdgeTransactions; 16 + } 17 + 18 + public function setEdgeTransactionDescriptions($add, $rem, $set) { 19 + $this->transactionDescriptions = array( 20 + '+' => $add, 21 + '-' => $rem, 22 + '=' => $set, 23 + ); 24 + return $this; 25 + } 26 + 27 + protected function newHTTPParameterType() { 28 + return new AphrontPHIDListHTTPParameterType(); 29 + } 30 + 31 + protected function getValueForTransaction() { 32 + $new = parent::getValueForTransaction(); 33 + 34 + if (!$this->getUseEdgeTransactions()) { 35 + return $new; 36 + } 37 + 38 + $old = $this->getInitialValue(); 39 + if ($old === null) { 40 + return array( 41 + '=' => array_fuse($new), 42 + ); 43 + } 44 + 45 + // If we're building an edge transaction and the request has data about the 46 + // original value the user saw when they loaded the form, interpret the 47 + // edit as a mixture of "+" and "-" operations instead of a single "=" 48 + // operation. This limits our exposure to race conditions by making most 49 + // concurrent edits merge correctly. 50 + 51 + $add = array_diff($new, $old); 52 + $rem = array_diff($old, $new); 53 + 54 + $value = array(); 55 + 56 + if ($add) { 57 + $value['+'] = array_fuse($add); 58 + } 59 + if ($rem) { 60 + $value['-'] = array_fuse($rem); 61 + } 62 + 63 + return $value; 64 + } 65 + 66 + protected function newEditType() { 67 + if ($this->getUseEdgeTransactions()) { 68 + return new PhabricatorEdgeEditType(); 69 + } 70 + 71 + return parent::newEditType(); 72 + } 73 + 74 + public function getEditTransactionTypes() { 75 + if (!$this->getUseEdgeTransactions()) { 76 + return parent::getEditTransactionTypes(); 77 + } 78 + 79 + $transaction_type = $this->getTransactionType(); 80 + if ($transaction_type === null) { 81 + return array(); 82 + } 83 + 84 + $type_key = $this->getEditTypeKey(); 85 + $strings = $this->transactionDescriptions; 86 + 87 + $base = $this->getEditTransactionType(); 88 + 89 + $add = id(clone $base) 90 + ->setEditType($type_key.'.add') 91 + ->setEdgeOperation('+') 92 + ->setDescription(idx($strings, '+')) 93 + ->setValueDescription(pht('List of PHIDs to add.')); 94 + 95 + $rem = id(clone $base) 96 + ->setEditType($type_key.'.remove') 97 + ->setEdgeOperation('-') 98 + ->setDescription(idx($strings, '-')) 99 + ->setValueDescription(pht('List of PHIDs to remove.')); 100 + 101 + $set = id(clone $base) 102 + ->setEditType($type_key.'.set') 103 + ->setEdgeOperation('=') 104 + ->setDescription(idx($strings, '=')) 105 + ->setValueDescription(pht('List of PHIDs to set.')); 106 + 107 + return array( 108 + $add, 109 + $rem, 110 + $set, 111 + ); 112 + } 113 + 114 + }
+6 -67
src/applications/transactions/editfield/PhabricatorTokenizerEditField.php
··· 1 1 <?php 2 2 3 3 abstract class PhabricatorTokenizerEditField 4 - extends PhabricatorEditField { 5 - 6 - private $originalValue; 4 + extends PhabricatorPHIDListEditField { 7 5 8 6 abstract protected function newDatasource(); 9 7 ··· 11 9 $control = id(new AphrontFormTokenizerControl()) 12 10 ->setDatasource($this->newDatasource()); 13 11 14 - if ($this->originalValue !== null) { 15 - $control->setOriginalValue($this->originalValue); 12 + $initial_value = $this->getInitialValue(); 13 + if ($initial_value !== null) { 14 + $control->setOriginalValue($initial_value); 16 15 } 17 16 18 17 return $control; 19 18 } 20 19 21 - public function setValue($value) { 22 - $this->originalValue = $value; 23 - return parent::setValue($value); 24 - } 25 - 26 - protected function getValueFromSubmit(AphrontRequest $request, $key) { 27 - // TODO: Maybe move this unusual read somewhere else so subclassing this 28 - // correctly is easier? 29 - $this->originalValue = $request->getArr($key.'.original'); 30 - 31 - return parent::getValueFromSubmit($request, $key); 32 - } 33 - 34 - protected function getValueForTransaction() { 35 - $new = parent::getValueForTransaction(); 36 - 37 - $edge_types = array( 38 - PhabricatorTransactions::TYPE_EDGE => true, 39 - PhabricatorTransactions::TYPE_SUBSCRIBERS => true, 40 - ); 41 - 42 - if (isset($edge_types[$this->getTransactionType()])) { 43 - if ($this->originalValue !== null) { 44 - // If we're building an edge transaction and the request has data 45 - // about the original value the user saw when they loaded the form, 46 - // interpret the edit as a mixture of "+" and "-" operations instead 47 - // of a single "=" operation. This limits our exposure to race 48 - // conditions by making most concurrent edits merge correctly. 49 - 50 - $new = parent::getValueForTransaction(); 51 - $old = $this->originalValue; 52 - 53 - $add = array_diff($new, $old); 54 - $rem = array_diff($old, $new); 55 - 56 - $value = array(); 57 - 58 - if ($add) { 59 - $value['+'] = array_fuse($add); 60 - } 61 - if ($rem) { 62 - $value['-'] = array_fuse($rem); 63 - } 64 - 65 - return $value; 66 - } else { 67 - 68 - if (!is_array($new)) { 69 - throw new Exception(print_r($new, true)); 70 - } 71 - 72 - return array( 73 - '=' => array_fuse($new), 74 - ); 75 - } 76 - } 77 - 78 - return $new; 79 - } 80 - 81 - protected function newHTTPParameterType() { 82 - return new AphrontPHIDListHTTPParameterType(); 20 + protected function getInitialValueFromSubmit(AphrontRequest $request, $key) { 21 + return $request->getArr($key.'.original'); 83 22 } 84 23 85 24 }