mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-26 21:49:08 +01:00
MetaMTA - add (basic) application emails and deploy to Maniphest
Summary: Ref T5952, T3404. This lays the basic plumbing for how this will work, all the way to deploying on Maniphest. Aside from what is mentioned on T5952, I think page(s) on editing application emails could use a little more helpful text about what's going on, similar to how the config page that's getting deprecated works. Test Plan: ran migration and noted my create email address migrated successfully. used bin/mail to make a task. added another email and used bin/mail to make a task. deleted an email. edited an email. invoked various error states and they all looked good. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T3404, T5952 Differential Revision: https://secure.phabricator.com/D11418
This commit is contained in:
parent
a15852c112
commit
53b06408f4
15 changed files with 695 additions and 21 deletions
12
resources/sql/autopatches/20150115.applicationemails.sql
Normal file
12
resources/sql/autopatches/20150115.applicationemails.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE {$NAMESPACE}_metamta.metamta_applicationemail (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
phid VARBINARY(64) NOT NULL,
|
||||||
|
applicationPHID VARBINARY(64) NOT NULL,
|
||||||
|
address VARCHAR(128) NOT NULL COLLATE {$COLLATE_SORT},
|
||||||
|
configData LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||||
|
dateCreated INT UNSIGNED NOT NULL,
|
||||||
|
dateModified INT UNSIGNED NOT NULL,
|
||||||
|
KEY `key_application` (applicationPHID),
|
||||||
|
UNIQUE KEY `key_address` (address),
|
||||||
|
UNIQUE KEY `key_phid` (phid)
|
||||||
|
) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$key = 'metamta.maniphest.public-create-email';
|
||||||
|
echo "Migrating `$key` to new application email infrastructure...\n";
|
||||||
|
$value = PhabricatorEnv::getEnvConfigIfExists($key);
|
||||||
|
$maniphest = new PhabricatorManiphestApplication();
|
||||||
|
|
||||||
|
if ($value) {
|
||||||
|
try {
|
||||||
|
PhabricatorMetaMTAApplicationEmail::initializeNewAppEmail(
|
||||||
|
PhabricatorUser::getOmnipotentUser())
|
||||||
|
->setAddress($value)
|
||||||
|
->setApplicationPHID($maniphest->getPHID())
|
||||||
|
->save();
|
||||||
|
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||||
|
// already migrated?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Done.\n";
|
|
@ -1258,6 +1258,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php',
|
'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php',
|
||||||
'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php',
|
'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php',
|
||||||
'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php',
|
'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php',
|
||||||
|
'PhabricatorApplicationEditEmailController' => 'applications/meta/controller/PhabricatorApplicationEditEmailController.php',
|
||||||
'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php',
|
'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php',
|
||||||
'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php',
|
'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php',
|
||||||
'PhabricatorApplicationSearchController' => 'applications/search/controller/PhabricatorApplicationSearchController.php',
|
'PhabricatorApplicationSearchController' => 'applications/search/controller/PhabricatorApplicationSearchController.php',
|
||||||
|
@ -1946,6 +1947,9 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMetaMTAActor' => 'applications/metamta/query/PhabricatorMetaMTAActor.php',
|
'PhabricatorMetaMTAActor' => 'applications/metamta/query/PhabricatorMetaMTAActor.php',
|
||||||
'PhabricatorMetaMTAActorQuery' => 'applications/metamta/query/PhabricatorMetaMTAActorQuery.php',
|
'PhabricatorMetaMTAActorQuery' => 'applications/metamta/query/PhabricatorMetaMTAActorQuery.php',
|
||||||
'PhabricatorMetaMTAApplication' => 'applications/metamta/application/PhabricatorMetaMTAApplication.php',
|
'PhabricatorMetaMTAApplication' => 'applications/metamta/application/PhabricatorMetaMTAApplication.php',
|
||||||
|
'PhabricatorMetaMTAApplicationEmail' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php',
|
||||||
|
'PhabricatorMetaMTAApplicationEmailPHIDType' => 'applications/phid/PhabricatorMetaMTAApplicationEmailPHIDType.php',
|
||||||
|
'PhabricatorMetaMTAApplicationEmailQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailQuery.php',
|
||||||
'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php',
|
'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php',
|
||||||
'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php',
|
'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php',
|
||||||
'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php',
|
'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php',
|
||||||
|
@ -4430,6 +4434,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource',
|
'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||||
'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController',
|
'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController',
|
||||||
'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController',
|
'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController',
|
||||||
|
'PhabricatorApplicationEditEmailController' => 'PhabricatorApplicationsController',
|
||||||
'PhabricatorApplicationLaunchView' => 'AphrontTagView',
|
'PhabricatorApplicationLaunchView' => 'AphrontTagView',
|
||||||
'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController',
|
'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController',
|
||||||
|
@ -5155,6 +5160,12 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMercurialGraphStream' => 'PhabricatorRepositoryGraphStream',
|
'PhabricatorMercurialGraphStream' => 'PhabricatorRepositoryGraphStream',
|
||||||
'PhabricatorMetaMTAActorQuery' => 'PhabricatorQuery',
|
'PhabricatorMetaMTAActorQuery' => 'PhabricatorQuery',
|
||||||
'PhabricatorMetaMTAApplication' => 'PhabricatorApplication',
|
'PhabricatorMetaMTAApplication' => 'PhabricatorApplication',
|
||||||
|
'PhabricatorMetaMTAApplicationEmail' => array(
|
||||||
|
'PhabricatorMetaMTADAO',
|
||||||
|
'PhabricatorPolicyInterface',
|
||||||
|
),
|
||||||
|
'PhabricatorMetaMTAApplicationEmailPHIDType' => 'PhabricatorPHIDType',
|
||||||
|
'PhabricatorMetaMTAApplicationEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||||
'PhabricatorMetaMTAController' => 'PhabricatorController',
|
'PhabricatorMetaMTAController' => 'PhabricatorController',
|
||||||
'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
|
'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* @task info Application Information
|
* @task info Application Information
|
||||||
* @task ui UI Integration
|
* @task ui UI Integration
|
||||||
* @task uri URI Routing
|
* @task uri URI Routing
|
||||||
|
* @task mail Email integration
|
||||||
* @task fact Fact Integration
|
* @task fact Fact Integration
|
||||||
* @task meta Application Management
|
* @task meta Application Management
|
||||||
*/
|
*/
|
||||||
|
@ -193,6 +194,22 @@ abstract class PhabricatorApplication implements PhabricatorPolicyInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( Email Integration )-------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function supportsEmailIntegration() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getInboundEmailSupportLink() {
|
||||||
|
return PhabricatorEnv::getDocLink('Configuring Inbound Email');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAppEmailBlurb() {
|
||||||
|
throw new Exception('Not Implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( Fact Integration )--------------------------------------------------- */
|
/* -( Fact Integration )--------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,20 @@ final class PhabricatorManiphestApplication extends PhabricatorApplication {
|
||||||
return $items;
|
return $items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function supportsEmailIntegration() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAppEmailBlurb() {
|
||||||
|
return pht(
|
||||||
|
'Send email to these addresses to create tasks. %s',
|
||||||
|
phutil_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $this->getInboundEmailSupportLink(),),
|
||||||
|
pht('Learn More')));
|
||||||
|
}
|
||||||
|
|
||||||
protected function getCustomCapabilities() {
|
protected function getCustomCapabilities() {
|
||||||
return array(
|
return array(
|
||||||
ManiphestDefaultViewCapability::CAPABILITY => array(
|
ManiphestDefaultViewCapability::CAPABILITY => array(
|
||||||
|
|
|
@ -294,9 +294,16 @@ EOTEXT
|
||||||
'metamta.maniphest.public-create-email',
|
'metamta.maniphest.public-create-email',
|
||||||
'string',
|
'string',
|
||||||
null)
|
null)
|
||||||
->setSummary(pht('Allow filing bugs via email.'))
|
->setLocked(true)
|
||||||
|
->setLockedMessage(pht(
|
||||||
|
'This configuration is deprecated. See description for details.'))
|
||||||
|
->setSummary(pht('DEPRECATED - Allow filing bugs via email.'))
|
||||||
->setDescription(
|
->setDescription(
|
||||||
pht(
|
pht(
|
||||||
|
'This config has been deprecated in favor of [[ '.
|
||||||
|
'/applications/view/PhabricatorManiphestApplication/ | '.
|
||||||
|
'application settings ]], which allow for multiple email '.
|
||||||
|
'addresses and other functionality.'."\n\n".
|
||||||
'You can configure an email address like '.
|
'You can configure an email address like '.
|
||||||
'"bugs@phabricator.example.com" which will automatically create '.
|
'"bugs@phabricator.example.com" which will automatically create '.
|
||||||
'Maniphest tasks when users send email to it. This relies on the '.
|
'Maniphest tasks when users send email to it. This relies on the '.
|
||||||
|
|
|
@ -8,12 +8,18 @@ final class ManiphestCreateMailReceiver extends PhabricatorMailReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function canAcceptMail(PhabricatorMetaMTAReceivedMail $mail) {
|
public function canAcceptMail(PhabricatorMetaMTAReceivedMail $mail) {
|
||||||
$config_key = 'metamta.maniphest.public-create-email';
|
$maniphest_app = new PhabricatorManiphestApplication();
|
||||||
$create_address = PhabricatorEnv::getEnvConfig($config_key);
|
$application_emails = id(new PhabricatorMetaMTAApplicationEmailQuery())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
|
->withApplicationPHIDs(array($maniphest_app->getPHID()))
|
||||||
|
->execute();
|
||||||
|
|
||||||
foreach ($mail->getToAddresses() as $to_address) {
|
foreach ($mail->getToAddresses() as $to_address) {
|
||||||
if ($this->matchAddresses($create_address, $to_address)) {
|
foreach ($application_emails as $application_email) {
|
||||||
return true;
|
$create_address = $application_email->getAddress();
|
||||||
|
if ($this->matchAddresses($create_address, $to_address)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,8 @@ final class PhabricatorApplicationsApplication extends PhabricatorApplication {
|
||||||
=> 'PhabricatorApplicationDetailViewController',
|
=> 'PhabricatorApplicationDetailViewController',
|
||||||
'edit/(?P<application>\w+)/'
|
'edit/(?P<application>\w+)/'
|
||||||
=> 'PhabricatorApplicationEditController',
|
=> 'PhabricatorApplicationEditController',
|
||||||
|
'editemail/(?P<application>\w+)/'
|
||||||
|
=> 'PhabricatorApplicationEditEmailController',
|
||||||
'(?P<application>\w+)/(?P<action>install|uninstall)/'
|
'(?P<application>\w+)/(?P<action>install|uninstall)/'
|
||||||
=> 'PhabricatorApplicationUninstallController',
|
=> 'PhabricatorApplicationUninstallController',
|
||||||
),
|
),
|
||||||
|
|
|
@ -3,23 +3,18 @@
|
||||||
final class PhabricatorApplicationDetailViewController
|
final class PhabricatorApplicationDetailViewController
|
||||||
extends PhabricatorApplicationsController {
|
extends PhabricatorApplicationsController {
|
||||||
|
|
||||||
private $application;
|
|
||||||
|
|
||||||
public function shouldAllowPublic() {
|
public function shouldAllowPublic() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function willProcessRequest(array $data) {
|
public function handleRequest(AphrontRequest $request) {
|
||||||
$this->application = $data['application'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function processRequest() {
|
|
||||||
$request = $this->getRequest();
|
|
||||||
$user = $request->getUser();
|
$user = $request->getUser();
|
||||||
|
$application = $request->getURIData('application');
|
||||||
|
|
||||||
$selected = id(new PhabricatorApplicationQuery())
|
$selected = id(new PhabricatorApplicationQuery())
|
||||||
->setViewer($user)
|
->setViewer($user)
|
||||||
->withClasses(array($this->application))
|
->withClasses(array($application))
|
||||||
->executeOne();
|
->executeOne();
|
||||||
if (!$selected) {
|
if (!$selected) {
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
|
@ -119,6 +114,26 @@ final class PhabricatorApplicationDetailViewController
|
||||||
idx($descriptions, $capability));
|
idx($descriptions, $capability));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($application->supportsEmailIntegration()) {
|
||||||
|
$properties->addSectionHeader(pht('Application Emails'));
|
||||||
|
$properties->addTextContent($application->getAppEmailBlurb());
|
||||||
|
$email_addresses = id(new PhabricatorMetaMTAApplicationEmailQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withApplicationPHIDs(array($application->getPHID()))
|
||||||
|
->execute();
|
||||||
|
if (empty($email_addresses)) {
|
||||||
|
$properties->addProperty(
|
||||||
|
null,
|
||||||
|
pht('No email addresses configured.'));
|
||||||
|
} else {
|
||||||
|
foreach ($email_addresses as $email_address) {
|
||||||
|
$properties->addProperty(
|
||||||
|
null,
|
||||||
|
$email_address->getAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $properties;
|
return $properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +168,18 @@ final class PhabricatorApplicationDetailViewController
|
||||||
->setWorkflow(!$can_edit)
|
->setWorkflow(!$can_edit)
|
||||||
->setHref($edit_uri));
|
->setHref($edit_uri));
|
||||||
|
|
||||||
|
if ($selected->supportsEmailIntegration()) {
|
||||||
|
$edit_email_uri = $this->getApplicationURI(
|
||||||
|
'editemail/'.get_class($selected).'/');
|
||||||
|
$view->addAction(
|
||||||
|
id(new PhabricatorActionView())
|
||||||
|
->setName(pht('Edit Application Emails'))
|
||||||
|
->setIcon('fa-envelope')
|
||||||
|
->setDisabled(!$can_edit)
|
||||||
|
->setWorkflow(!$can_edit)
|
||||||
|
->setHref($edit_email_uri));
|
||||||
|
}
|
||||||
|
|
||||||
if ($selected->canUninstall()) {
|
if ($selected->canUninstall()) {
|
||||||
if ($selected->isInstalled()) {
|
if ($selected->isInstalled()) {
|
||||||
$view->addAction(
|
$view->addAction(
|
||||||
|
|
|
@ -0,0 +1,307 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorApplicationEditEmailController
|
||||||
|
extends PhabricatorApplicationsController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
$application = $request->getURIData('application');
|
||||||
|
|
||||||
|
$application = id(new PhabricatorApplicationQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withClasses(array($application))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$application) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = $application->getName();
|
||||||
|
|
||||||
|
$uri = $request->getRequestURI();
|
||||||
|
$uri->setQueryParams(array());
|
||||||
|
|
||||||
|
$new = $request->getStr('new');
|
||||||
|
$edit = $request->getInt('edit');
|
||||||
|
$delete = $request->getInt('delete');
|
||||||
|
|
||||||
|
if ($new) {
|
||||||
|
return $this->returnNewAddressResponse($request, $uri, $application);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($edit) {
|
||||||
|
return $this->returnEditAddressResponse($request, $uri, $edit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($delete) {
|
||||||
|
return $this->returnDeleteAddressResponse($request, $uri, $delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
$emails = id(new PhabricatorMetaMTAApplicationEmailQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withApplicationPHIDs(array($application->getPHID()))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$highlight = $request->getInt('highlight');
|
||||||
|
$rowc = array();
|
||||||
|
$rows = array();
|
||||||
|
foreach ($emails as $email) {
|
||||||
|
|
||||||
|
$button_edit = javelin_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'class' => 'button small grey',
|
||||||
|
'href' => $uri->alter('edit', $email->getID()),
|
||||||
|
'sigil' => 'workflow',
|
||||||
|
),
|
||||||
|
pht('Edit'));
|
||||||
|
|
||||||
|
$button_remove = javelin_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'class' => 'button small grey',
|
||||||
|
'href' => $uri->alter('delete', $email->getID()),
|
||||||
|
'sigil' => 'workflow',
|
||||||
|
),
|
||||||
|
pht('Delete'));
|
||||||
|
|
||||||
|
if ($highlight == $email->getID()) {
|
||||||
|
$rowc[] = 'highlighted';
|
||||||
|
} else {
|
||||||
|
$rowc[] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows[] = array(
|
||||||
|
$email->getAddress(),
|
||||||
|
$button_edit,
|
||||||
|
$button_remove,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = id(new AphrontTableView($rows))
|
||||||
|
->setNoDataString(pht('No application emails created yet.'));
|
||||||
|
$table->setHeaders(
|
||||||
|
array(
|
||||||
|
pht('Email'),
|
||||||
|
pht('Edit'),
|
||||||
|
pht('Delete'),
|
||||||
|
));
|
||||||
|
$table->setColumnClasses(
|
||||||
|
array(
|
||||||
|
'wide',
|
||||||
|
'action',
|
||||||
|
'action',
|
||||||
|
));
|
||||||
|
$table->setRowClasses($rowc);
|
||||||
|
$table->setColumnVisibility(
|
||||||
|
array(
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
$form = id(new AphrontFormView())
|
||||||
|
->setUser($viewer);
|
||||||
|
|
||||||
|
$view_uri = $this->getApplicationURI('view/'.get_class($application).'/');
|
||||||
|
$crumbs = $this->buildApplicationCrumbs();
|
||||||
|
$crumbs->addTextCrumb($application->getName(), $view_uri);
|
||||||
|
$crumbs->addTextCrumb(pht('Edit Application Emails'));
|
||||||
|
|
||||||
|
$header = id(new PHUIHeaderView())
|
||||||
|
->setHeader(pht('Edit Application Emails: %s', $application->getName()));
|
||||||
|
|
||||||
|
$icon = id(new PHUIIconView())
|
||||||
|
->setIconFont('fa-plus');
|
||||||
|
$button = new PHUIButtonView();
|
||||||
|
$button->setText(pht('Add New Address'));
|
||||||
|
$button->setTag('a');
|
||||||
|
$button->setHref($uri->alter('new', 'true'));
|
||||||
|
$button->setIcon($icon);
|
||||||
|
$button->addSigil('workflow');
|
||||||
|
$header->addActionLink($button);
|
||||||
|
|
||||||
|
$object_box = id(new PHUIObjectBoxView())
|
||||||
|
->setHeader($header)
|
||||||
|
->appendChild($table)
|
||||||
|
->appendChild(
|
||||||
|
id(new PHUIBoxView())
|
||||||
|
->appendChild($application->getAppEmailBlurb())
|
||||||
|
->addPadding(PHUI::PADDING_MEDIUM));
|
||||||
|
|
||||||
|
return $this->buildApplicationPage(
|
||||||
|
array(
|
||||||
|
$crumbs,
|
||||||
|
$object_box,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => $title,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
PhabricatorApplication $application) {
|
||||||
|
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
$email_object =
|
||||||
|
PhabricatorMetaMTAApplicationEmail::initializeNewAppEmail($viewer)
|
||||||
|
->setApplicationPHID($application->getPHID());
|
||||||
|
|
||||||
|
return $this->returnSaveAddressResponse(
|
||||||
|
$request,
|
||||||
|
$uri,
|
||||||
|
$email_object,
|
||||||
|
$is_new = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function returnEditAddressResponse(
|
||||||
|
AphrontRequest $request,
|
||||||
|
PhutilURI $uri,
|
||||||
|
$email_object_id) {
|
||||||
|
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
$email_object = id(new PhabricatorMetaMTAApplicationEmailQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($email_object_id))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$email_object) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->returnSaveAddressResponse(
|
||||||
|
$request,
|
||||||
|
$uri,
|
||||||
|
$email_object,
|
||||||
|
$is_new = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function returnSaveAddressResponse(
|
||||||
|
AphrontRequest $request,
|
||||||
|
PhutilURI $uri,
|
||||||
|
PhabricatorMetaMTAApplicationEmail $email_object,
|
||||||
|
$is_new) {
|
||||||
|
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
|
$e_email = true;
|
||||||
|
$email = null;
|
||||||
|
$errors = array();
|
||||||
|
if ($request->isDialogFormPost()) {
|
||||||
|
$email = trim($request->getStr('email'));
|
||||||
|
list($e_email, $errors) = $this->validateApplicationEmail($email);
|
||||||
|
$email_object->setAddress($email);
|
||||||
|
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors) {
|
||||||
|
$errors = id(new AphrontErrorView())
|
||||||
|
->setErrors($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = id(new PHUIFormLayoutView())
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTextControl())
|
||||||
|
->setLabel(pht('Email'))
|
||||||
|
->setName('email')
|
||||||
|
->setValue($email_object->getAddress())
|
||||||
|
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
|
||||||
|
->setError($e_email));
|
||||||
|
|
||||||
|
$dialog = id(new AphrontDialogView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->setTitle(pht('New Address'))
|
||||||
|
->appendChild($errors)
|
||||||
|
->appendChild($form)
|
||||||
|
->addSubmitButton(pht('Save'))
|
||||||
|
->addCancelButton($uri);
|
||||||
|
|
||||||
|
if ($is_new) {
|
||||||
|
$dialog->addHiddenInput('new', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function returnDeleteAddressResponse(
|
||||||
|
AphrontRequest $request,
|
||||||
|
PhutilURI $uri,
|
||||||
|
$email_object_id) {
|
||||||
|
|
||||||
|
$viewer = $request->getUser();
|
||||||
|
$email_object = id(new PhabricatorMetaMTAApplicationEmailQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withIDs(array($email_object_id))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$email_object) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isDialogFormPost()) {
|
||||||
|
$email_object->delete();
|
||||||
|
return id(new AphrontRedirectResponse())->setURI($uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dialog = id(new AphrontDialogView())
|
||||||
|
->setUser($viewer)
|
||||||
|
->addHiddenInput('delete', $email_object_id)
|
||||||
|
->setTitle(pht('Delete Address'))
|
||||||
|
->appendParagraph(pht(
|
||||||
|
'Are you sure you want to delete this email address?'))
|
||||||
|
->addSubmitButton(pht('Delete'))
|
||||||
|
->addCancelButton($uri);
|
||||||
|
|
||||||
|
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorMetaMTAApplicationEmailQuery
|
||||||
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||||
|
|
||||||
|
private $ids;
|
||||||
|
private $phids;
|
||||||
|
private $addresses;
|
||||||
|
private $applicationPHIDs;
|
||||||
|
|
||||||
|
public function withIDs(array $ids) {
|
||||||
|
$this->ids = $ids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPHIDs(array $phids) {
|
||||||
|
$this->phids = $phids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withAddresses(array $addresses) {
|
||||||
|
$this->addresses = $addresses;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withApplicationPHIDs(array $phids) {
|
||||||
|
$this->applicationPHIDs = $phids;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadPage() {
|
||||||
|
$table = new PhabricatorMetaMTAApplicationEmail();
|
||||||
|
$conn_r = $table->establishConnection('r');
|
||||||
|
|
||||||
|
$data = queryfx_all(
|
||||||
|
$conn_r,
|
||||||
|
'SELECT * FROM %T appemail %Q %Q %Q %Q',
|
||||||
|
$table->getTableName(),
|
||||||
|
$this->buildWhereClause($conn_r),
|
||||||
|
$this->buildApplicationSearchGroupClause($conn_r),
|
||||||
|
$this->buildOrderClause($conn_r),
|
||||||
|
$this->buildLimitClause($conn_r));
|
||||||
|
|
||||||
|
return $table->loadAllFromArray($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function willFilterPage(array $app_emails) {
|
||||||
|
$app_emails_map = mgroup($app_emails, 'getApplicationPHID');
|
||||||
|
$applications = id(new PhabricatorApplicationQuery())
|
||||||
|
->setViewer($this->getViewer())
|
||||||
|
->withPHIDs(array_keys($app_emails_map))
|
||||||
|
->execute();
|
||||||
|
$applications = mpull($applications, null, 'getPHID');
|
||||||
|
|
||||||
|
foreach ($app_emails_map as $app_phid => $app_emails_group) {
|
||||||
|
foreach ($app_emails_group as $app_email) {
|
||||||
|
$application = idx($applications, $app_phid);
|
||||||
|
if (!$application) {
|
||||||
|
unset($app_emails[$app_phid]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$app_email->attachApplication($application);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $app_emails;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildWhereClause($conn_r) {
|
||||||
|
$where = array();
|
||||||
|
|
||||||
|
if ($this->addresses !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'appemail.address IN (%Ls)',
|
||||||
|
$this->addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->applicationPHIDs !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'appemail.applicationPHID IN (%Ls)',
|
||||||
|
$this->applicationPHIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->phids !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'appemail.phid IN (%Ls)',
|
||||||
|
$this->phids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->ids !== null) {
|
||||||
|
$where[] = qsprintf(
|
||||||
|
$conn_r,
|
||||||
|
'appemail.id IN (%Ld)',
|
||||||
|
$this->ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
$where[] = $this->buildPagingClause($conn_r);
|
||||||
|
|
||||||
|
return $this->formatWhereClause($where);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPagingColumn() {
|
||||||
|
return 'appemail.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getApplicationSearchObjectPHIDColumn() {
|
||||||
|
return 'appemail.phid';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQueryApplicationClass() {
|
||||||
|
return 'PhabricatorMetaMTAApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,6 +15,10 @@ abstract class PhabricatorMailReceiver {
|
||||||
$this->processReceivedMail($mail, $sender);
|
$this->processReceivedMail($mail, $sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getViewer() {
|
||||||
|
return PhabricatorUser::getOmnipotentUser();
|
||||||
|
}
|
||||||
|
|
||||||
public function validateSender(
|
public function validateSender(
|
||||||
PhabricatorMetaMTAReceivedMail $mail,
|
PhabricatorMetaMTAReceivedMail $mail,
|
||||||
PhabricatorUser $sender) {
|
PhabricatorUser $sender) {
|
||||||
|
@ -103,7 +107,7 @@ abstract class PhabricatorMailReceiver {
|
||||||
if ($allow_email_users) {
|
if ($allow_email_users) {
|
||||||
$from_obj = new PhutilEmailAddress($from);
|
$from_obj = new PhutilEmailAddress($from);
|
||||||
$xuser = id(new PhabricatorExternalAccountQuery())
|
$xuser = id(new PhabricatorExternalAccountQuery())
|
||||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
->setViewer($this->getViewer())
|
||||||
->withAccountTypes(array('email'))
|
->withAccountTypes(array('email'))
|
||||||
->withAccountDomains(array($from_obj->getDomainName(), 'self'))
|
->withAccountDomains(array($from_obj->getDomainName(), 'self'))
|
||||||
->withAccountIDs(array($from_obj->getAddress()))
|
->withAccountIDs(array($from_obj->getAddress()))
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorMetaMTAApplicationEmail
|
||||||
|
extends PhabricatorMetaMTADAO
|
||||||
|
implements PhabricatorPolicyInterface {
|
||||||
|
|
||||||
|
protected $applicationPHID;
|
||||||
|
protected $address;
|
||||||
|
protected $configData;
|
||||||
|
|
||||||
|
private $application = self::ATTACHABLE;
|
||||||
|
|
||||||
|
protected function getConfiguration() {
|
||||||
|
return array(
|
||||||
|
self::CONFIG_AUX_PHID => true,
|
||||||
|
self::CONFIG_SERIALIZATION => array(
|
||||||
|
'configData' => self::SERIALIZATION_JSON,
|
||||||
|
),
|
||||||
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
|
'address' => 'sort128',
|
||||||
|
),
|
||||||
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
|
'key_address' => array(
|
||||||
|
'columns' => array('address'),
|
||||||
|
'unique' => true,
|
||||||
|
),
|
||||||
|
'key_application' => array(
|
||||||
|
'columns' => array('applicationPHID'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) + parent::getConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generatePHID() {
|
||||||
|
return PhabricatorPHID::generateNewPHID(
|
||||||
|
PhabricatorMetaMTAApplicationEmailPHIDType::TYPECONST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function initializeNewAppEmail(PhabricatorUser $actor) {
|
||||||
|
return id(new PhabricatorMetaMTAApplicationEmail())
|
||||||
|
->setConfigData(array());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attachApplication(PhabricatorApplication $app) {
|
||||||
|
$this->application = $app;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplication() {
|
||||||
|
return self::assertAttached($this->application);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function getCapabilities() {
|
||||||
|
return array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPolicy($capability) {
|
||||||
|
return $this->getApplication()->getPolicy($capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAutomaticCapability(
|
||||||
|
$capability,
|
||||||
|
PhabricatorUser $viewer) {
|
||||||
|
|
||||||
|
return $this->getApplication()->hasAutomaticCapability(
|
||||||
|
$capability,
|
||||||
|
$viewer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAutomaticCapability($capability) {
|
||||||
|
return $this->getApplication()->describeAutomaticCapability($capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -64,10 +64,9 @@ final class PhabricatorMetaMTAReceivedMailTestCase extends PhabricatorTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDropUnknownSenderMail() {
|
public function testDropUnknownSenderMail() {
|
||||||
|
$this->setManiphestCreateEmail();
|
||||||
|
|
||||||
$env = PhabricatorEnv::beginScopedEnv();
|
$env = PhabricatorEnv::beginScopedEnv();
|
||||||
$env->overrideEnvConfig(
|
|
||||||
'metamta.maniphest.public-create-email',
|
|
||||||
'bugs@example.com');
|
|
||||||
$env->overrideEnvConfig('phabricator.allow-email-users', false);
|
$env->overrideEnvConfig('phabricator.allow-email-users', false);
|
||||||
$env->overrideEnvConfig('metamta.maniphest.default-public-author', null);
|
$env->overrideEnvConfig('metamta.maniphest.default-public-author', null);
|
||||||
|
|
||||||
|
@ -89,10 +88,7 @@ final class PhabricatorMetaMTAReceivedMailTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
|
|
||||||
public function testDropDisabledSenderMail() {
|
public function testDropDisabledSenderMail() {
|
||||||
$env = PhabricatorEnv::beginScopedEnv();
|
$this->setManiphestCreateEmail();
|
||||||
$env->overrideEnvConfig(
|
|
||||||
'metamta.maniphest.public-create-email',
|
|
||||||
'bugs@example.com');
|
|
||||||
|
|
||||||
$user = $this->generateNewTestUser()
|
$user = $this->generateNewTestUser()
|
||||||
->setIsDisabled(true)
|
->setIsDisabled(true)
|
||||||
|
@ -114,4 +110,15 @@ final class PhabricatorMetaMTAReceivedMailTestCase extends PhabricatorTestCase {
|
||||||
$mail->getStatus());
|
$mail->getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function setManiphestCreateEmail() {
|
||||||
|
$maniphest_app = new PhabricatorManiphestApplication();
|
||||||
|
try {
|
||||||
|
id(new PhabricatorMetaMTAApplicationEmail())
|
||||||
|
->setApplicationPHID($maniphest_app->getPHID())
|
||||||
|
->setAddress('bugs@example.com')
|
||||||
|
->setConfigData(array())
|
||||||
|
->save();
|
||||||
|
} catch (AphrontDuplicateKeyQueryException $ex) {}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorMetaMTAApplicationEmailPHIDType
|
||||||
|
extends PhabricatorPHIDType {
|
||||||
|
|
||||||
|
const TYPECONST = 'APPE';
|
||||||
|
|
||||||
|
public function getTypeName() {
|
||||||
|
return pht('Application Email');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPHIDTypeApplicationClass() {
|
||||||
|
return 'PhabricatorMetaMTAApplication';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTypeIcon() {
|
||||||
|
return 'fa-email bluegrey';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newObject() {
|
||||||
|
return new PhabricatorMetaMTAApplicationEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildQueryForObjects(
|
||||||
|
PhabricatorObjectQuery $query,
|
||||||
|
array $phids) {
|
||||||
|
|
||||||
|
return id(new PhabricatorMetaMTAApplicationEmailQuery())
|
||||||
|
->withPHIDs($phids);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadHandles(
|
||||||
|
PhabricatorHandleQuery $query,
|
||||||
|
array $handles,
|
||||||
|
array $objects) {
|
||||||
|
|
||||||
|
foreach ($handles as $phid => $handle) {
|
||||||
|
$email = $objects[$phid];
|
||||||
|
|
||||||
|
$handle->setName($email->getAddress());
|
||||||
|
$handle->setFullName($email->getAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue