mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-26 15:30:58 +01:00
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
This commit is contained in:
parent
325f4c863c
commit
6d6211d441
8 changed files with 308 additions and 64 deletions
19
resources/sql/autopatches/20150611.spaces.1.mailxaction.sql
Normal file
19
resources/sql/autopatches/20150611.spaces.1.mailxaction.sql
Normal file
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE {$NAMESPACE}_metamta.metamta_applicationemailtransaction (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
viewPolicy VARBINARY(64) NOT NULL,
|
||||
editPolicy VARBINARY(64) NOT NULL,
|
||||
commentPHID VARBINARY(64) DEFAULT NULL,
|
||||
commentVersion INT UNSIGNED NOT NULL,
|
||||
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (`phid`),
|
||||
KEY `key_object` (`objectPHID`)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -2051,9 +2051,12 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetaMTAApplication' => 'applications/metamta/application/PhabricatorMetaMTAApplication.php',
|
||||
'PhabricatorMetaMTAApplicationEmail' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php',
|
||||
'PhabricatorMetaMTAApplicationEmailDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php',
|
||||
'PhabricatorMetaMTAApplicationEmailEditor' => 'applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php',
|
||||
'PhabricatorMetaMTAApplicationEmailPHIDType' => 'applications/phid/PhabricatorMetaMTAApplicationEmailPHIDType.php',
|
||||
'PhabricatorMetaMTAApplicationEmailPanel' => 'applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php',
|
||||
'PhabricatorMetaMTAApplicationEmailQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailQuery.php',
|
||||
'PhabricatorMetaMTAApplicationEmailTransaction' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php',
|
||||
'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php',
|
||||
'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php',
|
||||
'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php',
|
||||
'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php',
|
||||
|
@ -5493,11 +5496,16 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetaMTAApplicationEmail' => array(
|
||||
'PhabricatorMetaMTADAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
),
|
||||
'PhabricatorMetaMTAApplicationEmailDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorMetaMTAApplicationEmailEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorMetaMTAApplicationEmailPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorMetaMTAApplicationEmailPanel' => 'PhabricatorApplicationConfigurationPanel',
|
||||
'PhabricatorMetaMTAApplicationEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorMetaMTAApplicationEmailTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorMetaMTAController' => 'PhabricatorController',
|
||||
'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
|
||||
|
|
|
@ -190,30 +190,6 @@ final class PhabricatorMetaMTAApplicationEmailPanel
|
|||
));
|
||||
}
|
||||
|
||||
private function validateApplicationEmail($email) {
|
||||
$errors = array();
|
||||
$e_email = true;
|
||||
|
||||
if (!strlen($email)) {
|
||||
$e_email = pht('Required');
|
||||
$errors[] = pht('Email is required.');
|
||||
} else if (!PhabricatorUserEmail::isValidAddress($email)) {
|
||||
$e_email = pht('Invalid');
|
||||
$errors[] = PhabricatorUserEmail::describeValidAddresses();
|
||||
} else if (!PhabricatorUserEmail::isAllowedAddress($email)) {
|
||||
$e_email = pht('Disallowed');
|
||||
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
|
||||
}
|
||||
$user_emails = id(new PhabricatorUserEmail())
|
||||
->loadAllWhere('address = %s', $email);
|
||||
if ($user_emails) {
|
||||
$e_email = pht('Duplicate');
|
||||
$errors[] = pht('A user already has this email.');
|
||||
}
|
||||
|
||||
return array($e_email, $errors);
|
||||
}
|
||||
|
||||
private function returnNewAddressResponse(
|
||||
AphrontRequest $request,
|
||||
PhutilURI $uri,
|
||||
|
@ -265,45 +241,59 @@ final class PhabricatorMetaMTAApplicationEmailPanel
|
|||
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$e_email = true;
|
||||
$email = null;
|
||||
$errors = array();
|
||||
$default_user_key =
|
||||
$config_default =
|
||||
PhabricatorMetaMTAApplicationEmail::CONFIG_DEFAULT_AUTHOR;
|
||||
|
||||
$e_email = true;
|
||||
$v_email = $email_object->getAddress();
|
||||
$v_default = $email_object->getConfigValue($config_default);
|
||||
|
||||
$validation_exception = null;
|
||||
if ($request->isDialogFormPost()) {
|
||||
$email = trim($request->getStr('email'));
|
||||
list($e_email, $errors) = $this->validateApplicationEmail($email);
|
||||
$email_object->setAddress($email);
|
||||
$default_user = $request->getArr($default_user_key);
|
||||
$default_user = reset($default_user);
|
||||
if ($default_user) {
|
||||
$email_object->setConfigValue($default_user_key, $default_user);
|
||||
}
|
||||
$e_email = null;
|
||||
|
||||
if (!$errors) {
|
||||
try {
|
||||
$email_object->save();
|
||||
return id(new AphrontRedirectResponse())->setURI(
|
||||
$uri->alter('highlight', $email_object->getID()));
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
$e_email = pht('Duplicate');
|
||||
$errors[] = pht(
|
||||
'Another application is already configured to use this email '.
|
||||
'address.');
|
||||
}
|
||||
$v_email = trim($request->getStr('email'));
|
||||
$v_default = $request->getArr($config_default);
|
||||
$v_default = nonempty(head($v_default), null);
|
||||
|
||||
$type_address =
|
||||
PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS;
|
||||
$type_config =
|
||||
PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG;
|
||||
|
||||
$key_config = PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG;
|
||||
|
||||
$xactions = array();
|
||||
|
||||
$xactions[] = id(new PhabricatorMetaMTAApplicationEmailTransaction())
|
||||
->setTransactionType($type_address)
|
||||
->setNewValue($v_email);
|
||||
|
||||
$xactions[] = id(new PhabricatorMetaMTAApplicationEmailTransaction())
|
||||
->setTransactionType($type_config)
|
||||
->setMetadataValue($key_config, $config_default)
|
||||
->setNewValue($v_default);
|
||||
|
||||
$editor = id(new PhabricatorMetaMTAApplicationEmailEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true);
|
||||
|
||||
try {
|
||||
$editor->applyTransactions($email_object, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI(
|
||||
$uri->alter('highlight', $email_object->getID()));
|
||||
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||
$validation_exception = $ex;
|
||||
$e_email = $ex->getShortMessage($type_address);
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
$errors = id(new PHUIInfoView())
|
||||
->setErrors($errors);
|
||||
}
|
||||
|
||||
$default_user = $email_object->getConfigValue($default_user_key);
|
||||
if ($default_user) {
|
||||
$default_user_value = array($default_user);
|
||||
if ($v_default) {
|
||||
$v_default = array($v_default);
|
||||
} else {
|
||||
$default_user_value = array();
|
||||
$v_default = array();
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
|
@ -312,28 +302,29 @@ final class PhabricatorMetaMTAApplicationEmailPanel
|
|||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Email'))
|
||||
->setName('email')
|
||||
->setValue($email_object->getAddress())
|
||||
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
|
||||
->setValue($v_email)
|
||||
->setError($e_email))
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setDatasource(new PhabricatorPeopleDatasource())
|
||||
->setLabel(pht('Default Author'))
|
||||
->setName($default_user_key)
|
||||
->setName($config_default)
|
||||
->setLimit(1)
|
||||
->setValue($default_user_value)
|
||||
->setValue($v_default)
|
||||
->setCaption(pht(
|
||||
'Used if the "From:" address does not map to a known account.')));
|
||||
|
||||
if ($is_new) {
|
||||
$title = pht('New Address');
|
||||
} else {
|
||||
$title = pht('Edit Address');
|
||||
}
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->setTitle($title)
|
||||
->appendChild($errors)
|
||||
->setValidationException($validation_exception)
|
||||
->appendForm($form)
|
||||
->addSubmitButton(pht('Save'))
|
||||
->addCancelButton($uri);
|
||||
|
@ -350,7 +341,8 @@ final class PhabricatorMetaMTAApplicationEmailPanel
|
|||
PhutilURI $uri,
|
||||
$email_object_id) {
|
||||
|
||||
$viewer = $request->getUser();
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$email_object = id(new PhabricatorMetaMTAApplicationEmailQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($email_object_id))
|
||||
|
@ -365,7 +357,8 @@ final class PhabricatorMetaMTAApplicationEmailPanel
|
|||
}
|
||||
|
||||
if ($request->isDialogFormPost()) {
|
||||
$email_object->delete();
|
||||
$engine = new PhabricatorDestructionEngine();
|
||||
$engine->destroyObject($email_object);
|
||||
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMetaMTAApplicationEmailEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return pht('PhabricatorMetaMTAApplication');
|
||||
}
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Application Emails');
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS;
|
||||
$types[] = PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
protected function getCustomTransactionOldValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
|
||||
return $object->getAddress();
|
||||
case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG:
|
||||
$key = $xaction->getMetadataValue(
|
||||
PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG);
|
||||
return $object->getConfigValue($key);
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function getCustomTransactionNewValue(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
|
||||
case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG:
|
||||
return $xaction->getNewValue();
|
||||
}
|
||||
|
||||
return parent::getCustomTransactionNewValue($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomInternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
$new = $xaction->getNewValue();
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
|
||||
$object->setAddress($new);
|
||||
return;
|
||||
case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG:
|
||||
$key = $xaction->getMetadataValue(
|
||||
PhabricatorMetaMTAApplicationEmailTransaction::KEY_CONFIG);
|
||||
$object->setConfigValue($key, $new);
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function applyCustomExternalTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
PhabricatorApplicationTransaction $xaction) {
|
||||
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
|
||||
case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_CONFIG:
|
||||
return;
|
||||
}
|
||||
|
||||
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
array $xactions) {
|
||||
|
||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||
|
||||
switch ($type) {
|
||||
case PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS:
|
||||
foreach ($xactions as $xaction) {
|
||||
$email = $xaction->getNewValue();
|
||||
if (!strlen($email)) {
|
||||
// We'll deal with this below.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!PhabricatorUserEmail::isValidAddress($email)) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Invalid'),
|
||||
pht('Email address is not formatted properly.'));
|
||||
}
|
||||
}
|
||||
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getAddress(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('You must provide an email address.'),
|
||||
nonempty(last($xactions), null));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
protected function didCatchDuplicateKeyException(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions,
|
||||
Exception $ex) {
|
||||
|
||||
$errors = array();
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
PhabricatorMetaMTAApplicationEmailTransaction::TYPE_ADDRESS,
|
||||
pht('Duplicate'),
|
||||
pht('This email address is already in use.'),
|
||||
null);
|
||||
|
||||
throw new PhabricatorApplicationTransactionValidationException($errors);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMetaMTAApplicationEmailTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new PhabricatorMetaMTAApplicationEmailTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
final class PhabricatorMetaMTAApplicationEmail
|
||||
extends PhabricatorMetaMTADAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorApplicationTransactionInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
|
||||
protected $applicationPHID;
|
||||
protected $address;
|
||||
|
@ -109,4 +112,35 @@ final class PhabricatorMetaMTAApplicationEmail
|
|||
return $this->getApplication()->describeAutomaticCapability($capability);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
public function getApplicationTransactionEditor() {
|
||||
return new PhabricatorMetaMTAApplicationEmailEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new PhabricatorMetaMTAApplicationEmailTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
||||
public function destroyObjectPermanently(
|
||||
PhabricatorDestructionEngine $engine) {
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMetaMTAApplicationEmailTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
const KEY_CONFIG = 'appemail.config.key';
|
||||
|
||||
const TYPE_ADDRESS = 'appemail.address';
|
||||
const TYPE_CONFIG = 'appemail.config';
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'metamta';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return PhabricatorMetaMTAApplicationEmailPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionCommentObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -844,6 +844,11 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
$object->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
$object->killTransaction();
|
||||
|
||||
// This callback has an opportunity to throw a better exception,
|
||||
// so execution may end here.
|
||||
$this->didCatchDuplicateKeyException($object, $xactions, $ex);
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
|
@ -1021,6 +1026,13 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
return $xactions;
|
||||
}
|
||||
|
||||
protected function didCatchDuplicateKeyException(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions,
|
||||
Exception $ex) {
|
||||
return;
|
||||
}
|
||||
|
||||
public function publishTransactions(
|
||||
PhabricatorLiskDAO $object,
|
||||
array $xactions) {
|
||||
|
|
Loading…
Reference in a new issue