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

Use ApplicationTransactions in ApplicationEmail

Summary:
Ref T8498. I want to add Spaces to these, and the logic for getting Spaces right is a bit tricky, so swap these to ApplicationTransactions.

One new piece of tech: made it easier for Editors to raise DuplicateKeyException as a normal ValidationException, so callers don't have to handle this case specially.

One behavioral change: we no longer require these addresses to be at the `auth.email-domains` domains -- I think this wasn't quite right in the general case. It's OK to require users to have `@mycompany.com` addresses but add `@phabricator.mycompany-infrastructure.com` addresses here if you want.

Test Plan:
- Tried to create a duplicate email.
- Tried to create an empty email.
- Tried to create an invalid email.
- Created a new email.
- Deleted an email.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T8498

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

+308 -64
+19
resources/sql/autopatches/20150611.spaces.1.mailxaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_metamta.metamta_applicationemailtransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + objectPHID VARBINARY(64) NOT NULL, 6 + viewPolicy VARBINARY(64) NOT NULL, 7 + editPolicy VARBINARY(64) NOT NULL, 8 + commentPHID VARBINARY(64) DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, 11 + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 12 + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 13 + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 14 + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (`phid`), 18 + KEY `key_object` (`objectPHID`) 19 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+8
src/__phutil_library_map__.php
··· 2051 2051 'PhabricatorMetaMTAApplication' => 'applications/metamta/application/PhabricatorMetaMTAApplication.php', 2052 2052 'PhabricatorMetaMTAApplicationEmail' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php', 2053 2053 'PhabricatorMetaMTAApplicationEmailDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php', 2054 + 'PhabricatorMetaMTAApplicationEmailEditor' => 'applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php', 2054 2055 'PhabricatorMetaMTAApplicationEmailPHIDType' => 'applications/phid/PhabricatorMetaMTAApplicationEmailPHIDType.php', 2055 2056 'PhabricatorMetaMTAApplicationEmailPanel' => 'applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php', 2056 2057 'PhabricatorMetaMTAApplicationEmailQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailQuery.php', 2058 + 'PhabricatorMetaMTAApplicationEmailTransaction' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php', 2059 + 'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php', 2057 2060 'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php', 2058 2061 'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php', 2059 2062 'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php', ··· 5493 5496 'PhabricatorMetaMTAApplicationEmail' => array( 5494 5497 'PhabricatorMetaMTADAO', 5495 5498 'PhabricatorPolicyInterface', 5499 + 'PhabricatorApplicationTransactionInterface', 5500 + 'PhabricatorDestructibleInterface', 5496 5501 ), 5497 5502 'PhabricatorMetaMTAApplicationEmailDatasource' => 'PhabricatorTypeaheadDatasource', 5503 + 'PhabricatorMetaMTAApplicationEmailEditor' => 'PhabricatorApplicationTransactionEditor', 5498 5504 'PhabricatorMetaMTAApplicationEmailPHIDType' => 'PhabricatorPHIDType', 5499 5505 'PhabricatorMetaMTAApplicationEmailPanel' => 'PhabricatorApplicationConfigurationPanel', 5500 5506 'PhabricatorMetaMTAApplicationEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5507 + 'PhabricatorMetaMTAApplicationEmailTransaction' => 'PhabricatorApplicationTransaction', 5508 + 'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 5501 5509 'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions', 5502 5510 'PhabricatorMetaMTAController' => 'PhabricatorController', 5503 5511 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
+56 -63
src/applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php
··· 190 190 )); 191 191 } 192 192 193 - private function validateApplicationEmail($email) { 194 - $errors = array(); 195 - $e_email = true; 196 - 197 - if (!strlen($email)) { 198 - $e_email = pht('Required'); 199 - $errors[] = pht('Email is required.'); 200 - } else if (!PhabricatorUserEmail::isValidAddress($email)) { 201 - $e_email = pht('Invalid'); 202 - $errors[] = PhabricatorUserEmail::describeValidAddresses(); 203 - } else if (!PhabricatorUserEmail::isAllowedAddress($email)) { 204 - $e_email = pht('Disallowed'); 205 - $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); 206 - } 207 - $user_emails = id(new PhabricatorUserEmail()) 208 - ->loadAllWhere('address = %s', $email); 209 - if ($user_emails) { 210 - $e_email = pht('Duplicate'); 211 - $errors[] = pht('A user already has this email.'); 212 - } 213 - 214 - return array($e_email, $errors); 215 - } 216 - 217 193 private function returnNewAddressResponse( 218 194 AphrontRequest $request, 219 195 PhutilURI $uri, ··· 265 241 266 242 $viewer = $request->getUser(); 267 243 244 + $config_default = 245 + PhabricatorMetaMTAApplicationEmail::CONFIG_DEFAULT_AUTHOR; 246 + 268 247 $e_email = true; 269 - $email = null; 270 - $errors = array(); 271 - $default_user_key = 272 - PhabricatorMetaMTAApplicationEmail::CONFIG_DEFAULT_AUTHOR; 248 + $v_email = $email_object->getAddress(); 249 + $v_default = $email_object->getConfigValue($config_default); 250 + 251 + $validation_exception = null; 273 252 if ($request->isDialogFormPost()) { 274 - $email = trim($request->getStr('email')); 275 - list($e_email, $errors) = $this->validateApplicationEmail($email); 276 - $email_object->setAddress($email); 277 - $default_user = $request->getArr($default_user_key); 278 - $default_user = reset($default_user); 279 - if ($default_user) { 280 - $email_object->setConfigValue($default_user_key, $default_user); 281 - } 253 + $e_email = null; 254 + 255 + $v_email = trim($request->getStr('email')); 256 + $v_default = $request->getArr($config_default); 257 + $v_default = nonempty(head($v_default), null); 258 + 259 + $type_address = 260 + PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS; 261 + $type_config = 262 + PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG; 263 + 264 + $key_config = PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG; 265 + 266 + $xactions = array(); 267 + 268 + $xactions[] = id(new PhabricatorMetaMTAApplicationEmailTransaction()) 269 + ->setTransactionType($type_address) 270 + ->setNewValue($v_email); 271 + 272 + $xactions[] = id(new PhabricatorMetaMTAApplicationEmailTransaction()) 273 + ->setTransactionType($type_config) 274 + ->setMetadataValue($key_config, $config_default) 275 + ->setNewValue($v_default); 276 + 277 + $editor = id(new PhabricatorMetaMTAApplicationEmailEditor()) 278 + ->setActor($viewer) 279 + ->setContentSourceFromRequest($request) 280 + ->setContinueOnNoEffect(true); 282 281 283 - if (!$errors) { 284 - try { 285 - $email_object->save(); 286 - return id(new AphrontRedirectResponse())->setURI( 287 - $uri->alter('highlight', $email_object->getID())); 288 - } catch (AphrontDuplicateKeyQueryException $ex) { 289 - $e_email = pht('Duplicate'); 290 - $errors[] = pht( 291 - 'Another application is already configured to use this email '. 292 - 'address.'); 293 - } 282 + try { 283 + $editor->applyTransactions($email_object, $xactions); 284 + 285 + return id(new AphrontRedirectResponse())->setURI( 286 + $uri->alter('highlight', $email_object->getID())); 287 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 288 + $validation_exception = $ex; 289 + $e_email = $ex->getShortMessage($type_address); 294 290 } 295 291 } 296 292 297 - if ($errors) { 298 - $errors = id(new PHUIInfoView()) 299 - ->setErrors($errors); 300 - } 301 - 302 - $default_user = $email_object->getConfigValue($default_user_key); 303 - if ($default_user) { 304 - $default_user_value = array($default_user); 293 + if ($v_default) { 294 + $v_default = array($v_default); 305 295 } else { 306 - $default_user_value = array(); 296 + $v_default = array(); 307 297 } 308 298 309 299 $form = id(new AphrontFormView()) ··· 312 302 id(new AphrontFormTextControl()) 313 303 ->setLabel(pht('Email')) 314 304 ->setName('email') 315 - ->setValue($email_object->getAddress()) 316 - ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) 305 + ->setValue($v_email) 317 306 ->setError($e_email)) 318 307 ->appendControl( 319 308 id(new AphrontFormTokenizerControl()) 320 309 ->setDatasource(new PhabricatorPeopleDatasource()) 321 310 ->setLabel(pht('Default Author')) 322 - ->setName($default_user_key) 311 + ->setName($config_default) 323 312 ->setLimit(1) 324 - ->setValue($default_user_value) 313 + ->setValue($v_default) 325 314 ->setCaption(pht( 326 315 'Used if the "From:" address does not map to a known account.'))); 316 + 327 317 if ($is_new) { 328 318 $title = pht('New Address'); 329 319 } else { 330 320 $title = pht('Edit Address'); 331 321 } 322 + 332 323 $dialog = id(new AphrontDialogView()) 333 324 ->setUser($viewer) 334 325 ->setWidth(AphrontDialogView::WIDTH_FORM) 335 326 ->setTitle($title) 336 - ->appendChild($errors) 327 + ->setValidationException($validation_exception) 337 328 ->appendForm($form) 338 329 ->addSubmitButton(pht('Save')) 339 330 ->addCancelButton($uri); ··· 350 341 PhutilURI $uri, 351 342 $email_object_id) { 352 343 353 - $viewer = $request->getUser(); 344 + $viewer = $this->getViewer(); 345 + 354 346 $email_object = id(new PhabricatorMetaMTAApplicationEmailQuery()) 355 347 ->setViewer($viewer) 356 348 ->withIDs(array($email_object_id)) ··· 365 357 } 366 358 367 359 if ($request->isDialogFormPost()) { 368 - $email_object->delete(); 360 + $engine = new PhabricatorDestructionEngine(); 361 + $engine->destroyObject($email_object); 369 362 return id(new AphrontRedirectResponse())->setURI($uri); 370 363 } 371 364
+145
src/applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAApplicationEmailEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return pht('PhabricatorMetaMTAApplication'); 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Application Emails'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS; 18 + $types[] = PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG; 19 + 20 + return $types; 21 + } 22 + 23 + protected function getCustomTransactionOldValue( 24 + PhabricatorLiskDAO $object, 25 + PhabricatorApplicationTransaction $xaction) { 26 + 27 + switch ($xaction->getTransactionType()) { 28 + case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS: 29 + return $object->getAddress(); 30 + case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG: 31 + $key = $xaction->getMetadataValue( 32 + PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG); 33 + return $object->getConfigValue($key); 34 + } 35 + 36 + return parent::getCustomTransactionOldValue($object, $xaction); 37 + } 38 + 39 + protected function getCustomTransactionNewValue( 40 + PhabricatorLiskDAO $object, 41 + PhabricatorApplicationTransaction $xaction) { 42 + 43 + switch ($xaction->getTransactionType()) { 44 + case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS: 45 + case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG: 46 + return $xaction->getNewValue(); 47 + } 48 + 49 + return parent::getCustomTransactionNewValue($object, $xaction); 50 + } 51 + 52 + protected function applyCustomInternalTransaction( 53 + PhabricatorLiskDAO $object, 54 + PhabricatorApplicationTransaction $xaction) { 55 + 56 + $new = $xaction->getNewValue(); 57 + 58 + switch ($xaction->getTransactionType()) { 59 + case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS: 60 + $object->setAddress($new); 61 + return; 62 + case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG: 63 + $key = $xaction->getMetadataValue( 64 + PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG); 65 + $object->setConfigValue($key, $new); 66 + return; 67 + } 68 + 69 + return parent::applyCustomInternalTransaction($object, $xaction); 70 + } 71 + 72 + protected function applyCustomExternalTransaction( 73 + PhabricatorLiskDAO $object, 74 + PhabricatorApplicationTransaction $xaction) { 75 + 76 + switch ($xaction->getTransactionType()) { 77 + case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS: 78 + case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG: 79 + return; 80 + } 81 + 82 + return parent::applyCustomExternalTransaction($object, $xaction); 83 + } 84 + 85 + protected function validateTransaction( 86 + PhabricatorLiskDAO $object, 87 + $type, 88 + array $xactions) { 89 + 90 + $errors = parent::validateTransaction($object, $type, $xactions); 91 + 92 + switch ($type) { 93 + case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS: 94 + foreach ($xactions as $xaction) { 95 + $email = $xaction->getNewValue(); 96 + if (!strlen($email)) { 97 + // We'll deal with this below. 98 + continue; 99 + } 100 + 101 + if (!PhabricatorUserEmail::isValidAddress($email)) { 102 + $errors[] = new PhabricatorApplicationTransactionValidationError( 103 + $type, 104 + pht('Invalid'), 105 + pht('Email address is not formatted properly.')); 106 + } 107 + } 108 + 109 + $missing = $this->validateIsEmptyTextField( 110 + $object->getAddress(), 111 + $xactions); 112 + 113 + if ($missing) { 114 + $error = new PhabricatorApplicationTransactionValidationError( 115 + $type, 116 + pht('Required'), 117 + pht('You must provide an email address.'), 118 + nonempty(last($xactions), null)); 119 + 120 + $error->setIsMissingFieldError(true); 121 + $errors[] = $error; 122 + } 123 + break; 124 + } 125 + 126 + return $errors; 127 + } 128 + 129 + protected function didCatchDuplicateKeyException( 130 + PhabricatorLiskDAO $object, 131 + array $xactions, 132 + Exception $ex) { 133 + 134 + $errors = array(); 135 + $errors[] = new PhabricatorApplicationTransactionValidationError( 136 + PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS, 137 + pht('Duplicate'), 138 + pht('This email address is already in use.'), 139 + null); 140 + 141 + throw new PhabricatorApplicationTransactionValidationException($errors); 142 + } 143 + 144 + 145 + }
+10
src/applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAApplicationEmailTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new PhabricatorMetaMTAApplicationEmailTransaction(); 8 + } 9 + 10 + }
+35 -1
src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php
··· 2 2 3 3 final class PhabricatorMetaMTAApplicationEmail 4 4 extends PhabricatorMetaMTADAO 5 - implements PhabricatorPolicyInterface { 5 + implements 6 + PhabricatorPolicyInterface, 7 + PhabricatorApplicationTransactionInterface, 8 + PhabricatorDestructibleInterface { 6 9 7 10 protected $applicationPHID; 8 11 protected $address; ··· 107 110 108 111 public function describeAutomaticCapability($capability) { 109 112 return $this->getApplication()->describeAutomaticCapability($capability); 113 + } 114 + 115 + 116 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 117 + 118 + 119 + public function getApplicationTransactionEditor() { 120 + return new PhabricatorMetaMTAApplicationEmailEditor(); 121 + } 122 + 123 + public function getApplicationTransactionObject() { 124 + return $this; 125 + } 126 + 127 + public function getApplicationTransactionTemplate() { 128 + return new PhabricatorMetaMTAApplicationEmailTransaction(); 129 + } 130 + 131 + public function willRenderTimeline( 132 + PhabricatorApplicationTransactionView $timeline, 133 + AphrontRequest $request) { 134 + return $timeline; 135 + } 136 + 137 + 138 + /* -( PhabricatorDestructibleInterface )----------------------------------- */ 139 + 140 + 141 + public function destroyObjectPermanently( 142 + PhabricatorDestructionEngine $engine) { 143 + $this->delete(); 110 144 } 111 145 112 146 }
+23
src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAApplicationEmailTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const KEY_CONFIG = 'appemail.config.key'; 7 + 8 + const TYPE_ADDRESS = 'appemail.address'; 9 + const TYPE_CONFIG = 'appemail.config'; 10 + 11 + public function getApplicationName() { 12 + return 'metamta'; 13 + } 14 + 15 + public function getApplicationTransactionType() { 16 + return PhabricatorMetaMTAApplicationEmailPHIDType::TYPECONST; 17 + } 18 + 19 + public function getApplicationTransactionCommentObject() { 20 + return null; 21 + } 22 + 23 + }
+12
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 844 844 $object->save(); 845 845 } catch (AphrontDuplicateKeyQueryException $ex) { 846 846 $object->killTransaction(); 847 + 848 + // This callback has an opportunity to throw a better exception, 849 + // so execution may end here. 850 + $this->didCatchDuplicateKeyException($object, $xactions, $ex); 851 + 847 852 throw $ex; 848 853 } 849 854 ··· 1019 1024 )); 1020 1025 1021 1026 return $xactions; 1027 + } 1028 + 1029 + protected function didCatchDuplicateKeyException( 1030 + PhabricatorLiskDAO $object, 1031 + array $xactions, 1032 + Exception $ex) { 1033 + return; 1022 1034 } 1023 1035 1024 1036 public function publishTransactions(