@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

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

+695 -21
+12
resources/sql/autopatches/20150115.applicationemails.sql
··· 1 + CREATE TABLE {$NAMESPACE}_metamta.metamta_applicationemail ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + applicationPHID VARBINARY(64) NOT NULL, 5 + address VARCHAR(128) NOT NULL COLLATE {$COLLATE_SORT}, 6 + configData LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, 7 + dateCreated INT UNSIGNED NOT NULL, 8 + dateModified INT UNSIGNED NOT NULL, 9 + KEY `key_application` (applicationPHID), 10 + UNIQUE KEY `key_address` (address), 11 + UNIQUE KEY `key_phid` (phid) 12 + ) ENGINE=MyISAM DEFAULT CHARSET={$CHARSET} COLLATE={$COLLATE_TEXT};
+20
resources/sql/autopatches/20150116.maniphestapplicationemails.php
··· 1 + <?php 2 + 3 + $key = 'metamta.maniphest.public-create-email'; 4 + echo "Migrating `$key` to new application email infrastructure...\n"; 5 + $value = PhabricatorEnv::getEnvConfigIfExists($key); 6 + $maniphest = new PhabricatorManiphestApplication(); 7 + 8 + if ($value) { 9 + try { 10 + PhabricatorMetaMTAApplicationEmail::initializeNewAppEmail( 11 + PhabricatorUser::getOmnipotentUser()) 12 + ->setAddress($value) 13 + ->setApplicationPHID($maniphest->getPHID()) 14 + ->save(); 15 + } catch (AphrontDuplicateKeyQueryException $ex) { 16 + // already migrated? 17 + } 18 + } 19 + 20 + echo "Done.\n";
+11
src/__phutil_library_map__.php
··· 1258 1258 'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php', 1259 1259 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 1260 1260 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', 1261 + 'PhabricatorApplicationEditEmailController' => 'applications/meta/controller/PhabricatorApplicationEditEmailController.php', 1261 1262 'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php', 1262 1263 'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php', 1263 1264 'PhabricatorApplicationSearchController' => 'applications/search/controller/PhabricatorApplicationSearchController.php', ··· 1946 1947 'PhabricatorMetaMTAActor' => 'applications/metamta/query/PhabricatorMetaMTAActor.php', 1947 1948 'PhabricatorMetaMTAActorQuery' => 'applications/metamta/query/PhabricatorMetaMTAActorQuery.php', 1948 1949 'PhabricatorMetaMTAApplication' => 'applications/metamta/application/PhabricatorMetaMTAApplication.php', 1950 + 'PhabricatorMetaMTAApplicationEmail' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php', 1951 + 'PhabricatorMetaMTAApplicationEmailPHIDType' => 'applications/phid/PhabricatorMetaMTAApplicationEmailPHIDType.php', 1952 + 'PhabricatorMetaMTAApplicationEmailQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailQuery.php', 1949 1953 'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php', 1950 1954 'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php', 1951 1955 'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php', ··· 4430 4434 'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource', 4431 4435 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 4432 4436 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', 4437 + 'PhabricatorApplicationEditEmailController' => 'PhabricatorApplicationsController', 4433 4438 'PhabricatorApplicationLaunchView' => 'AphrontTagView', 4434 4439 'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4435 4440 'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController', ··· 5155 5160 'PhabricatorMercurialGraphStream' => 'PhabricatorRepositoryGraphStream', 5156 5161 'PhabricatorMetaMTAActorQuery' => 'PhabricatorQuery', 5157 5162 'PhabricatorMetaMTAApplication' => 'PhabricatorApplication', 5163 + 'PhabricatorMetaMTAApplicationEmail' => array( 5164 + 'PhabricatorMetaMTADAO', 5165 + 'PhabricatorPolicyInterface', 5166 + ), 5167 + 'PhabricatorMetaMTAApplicationEmailPHIDType' => 'PhabricatorPHIDType', 5168 + 'PhabricatorMetaMTAApplicationEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5158 5169 'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions', 5159 5170 'PhabricatorMetaMTAController' => 'PhabricatorController', 5160 5171 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
+17
src/applications/base/PhabricatorApplication.php
··· 4 4 * @task info Application Information 5 5 * @task ui UI Integration 6 6 * @task uri URI Routing 7 + * @task mail Email integration 7 8 * @task fact Fact Integration 8 9 * @task meta Application Management 9 10 */ ··· 190 191 191 192 public function getRoutes() { 192 193 return array(); 194 + } 195 + 196 + 197 + /* -( Email Integration )-------------------------------------------------- */ 198 + 199 + 200 + public function supportsEmailIntegration() { 201 + return false; 202 + } 203 + 204 + protected function getInboundEmailSupportLink() { 205 + return PhabricatorEnv::getDocLink('Configuring Inbound Email'); 206 + } 207 + 208 + public function getAppEmailBlurb() { 209 + throw new Exception('Not Implemented.'); 193 210 } 194 211 195 212
+14
src/applications/maniphest/application/PhabricatorManiphestApplication.php
··· 109 109 return $items; 110 110 } 111 111 112 + public function supportsEmailIntegration() { 113 + return true; 114 + } 115 + 116 + public function getAppEmailBlurb() { 117 + return pht( 118 + 'Send email to these addresses to create tasks. %s', 119 + phutil_tag( 120 + 'a', 121 + array( 122 + 'href' => $this->getInboundEmailSupportLink(),), 123 + pht('Learn More'))); 124 + } 125 + 112 126 protected function getCustomCapabilities() { 113 127 return array( 114 128 ManiphestDefaultViewCapability::CAPABILITY => array(
+8 -1
src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php
··· 294 294 'metamta.maniphest.public-create-email', 295 295 'string', 296 296 null) 297 - ->setSummary(pht('Allow filing bugs via email.')) 297 + ->setLocked(true) 298 + ->setLockedMessage(pht( 299 + 'This configuration is deprecated. See description for details.')) 300 + ->setSummary(pht('DEPRECATED - Allow filing bugs via email.')) 298 301 ->setDescription( 299 302 pht( 303 + 'This config has been deprecated in favor of [[ '. 304 + '/applications/view/PhabricatorManiphestApplication/ | '. 305 + 'application settings ]], which allow for multiple email '. 306 + 'addresses and other functionality.'."\n\n". 300 307 'You can configure an email address like '. 301 308 '"bugs@phabricator.example.com" which will automatically create '. 302 309 'Maniphest tasks when users send email to it. This relies on the '.
+10 -4
src/applications/maniphest/mail/ManiphestCreateMailReceiver.php
··· 8 8 } 9 9 10 10 public function canAcceptMail(PhabricatorMetaMTAReceivedMail $mail) { 11 - $config_key = 'metamta.maniphest.public-create-email'; 12 - $create_address = PhabricatorEnv::getEnvConfig($config_key); 11 + $maniphest_app = new PhabricatorManiphestApplication(); 12 + $application_emails = id(new PhabricatorMetaMTAApplicationEmailQuery()) 13 + ->setViewer($this->getViewer()) 14 + ->withApplicationPHIDs(array($maniphest_app->getPHID())) 15 + ->execute(); 13 16 14 17 foreach ($mail->getToAddresses() as $to_address) { 15 - if ($this->matchAddresses($create_address, $to_address)) { 16 - return true; 18 + foreach ($application_emails as $application_email) { 19 + $create_address = $application_email->getAddress(); 20 + if ($this->matchAddresses($create_address, $to_address)) { 21 + return true; 22 + } 17 23 } 18 24 } 19 25
+2
src/applications/meta/application/PhabricatorApplicationsApplication.php
··· 41 41 => 'PhabricatorApplicationDetailViewController', 42 42 'edit/(?P<application>\w+)/' 43 43 => 'PhabricatorApplicationEditController', 44 + 'editemail/(?P<application>\w+)/' 45 + => 'PhabricatorApplicationEditEmailController', 44 46 '(?P<application>\w+)/(?P<action>install|uninstall)/' 45 47 => 'PhabricatorApplicationUninstallController', 46 48 ),
+35 -8
src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
··· 3 3 final class PhabricatorApplicationDetailViewController 4 4 extends PhabricatorApplicationsController { 5 5 6 - private $application; 7 6 8 7 public function shouldAllowPublic() { 9 8 return true; 10 9 } 11 10 12 - public function willProcessRequest(array $data) { 13 - $this->application = $data['application']; 14 - } 15 - 16 - public function processRequest() { 17 - $request = $this->getRequest(); 11 + public function handleRequest(AphrontRequest $request) { 18 12 $user = $request->getUser(); 13 + $application = $request->getURIData('application'); 19 14 20 15 $selected = id(new PhabricatorApplicationQuery()) 21 16 ->setViewer($user) 22 - ->withClasses(array($this->application)) 17 + ->withClasses(array($application)) 23 18 ->executeOne(); 24 19 if (!$selected) { 25 20 return new Aphront404Response(); ··· 119 114 idx($descriptions, $capability)); 120 115 } 121 116 117 + if ($application->supportsEmailIntegration()) { 118 + $properties->addSectionHeader(pht('Application Emails')); 119 + $properties->addTextContent($application->getAppEmailBlurb()); 120 + $email_addresses = id(new PhabricatorMetaMTAApplicationEmailQuery()) 121 + ->setViewer($viewer) 122 + ->withApplicationPHIDs(array($application->getPHID())) 123 + ->execute(); 124 + if (empty($email_addresses)) { 125 + $properties->addProperty( 126 + null, 127 + pht('No email addresses configured.')); 128 + } else { 129 + foreach ($email_addresses as $email_address) { 130 + $properties->addProperty( 131 + null, 132 + $email_address->getAddress()); 133 + } 134 + } 135 + } 136 + 122 137 return $properties; 123 138 } 124 139 ··· 152 167 ->setDisabled(!$can_edit) 153 168 ->setWorkflow(!$can_edit) 154 169 ->setHref($edit_uri)); 170 + 171 + if ($selected->supportsEmailIntegration()) { 172 + $edit_email_uri = $this->getApplicationURI( 173 + 'editemail/'.get_class($selected).'/'); 174 + $view->addAction( 175 + id(new PhabricatorActionView()) 176 + ->setName(pht('Edit Application Emails')) 177 + ->setIcon('fa-envelope') 178 + ->setDisabled(!$can_edit) 179 + ->setWorkflow(!$can_edit) 180 + ->setHref($edit_email_uri)); 181 + } 155 182 156 183 if ($selected->canUninstall()) { 157 184 if ($selected->isInstalled()) {
+307
src/applications/meta/controller/PhabricatorApplicationEditEmailController.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationEditEmailController 4 + extends PhabricatorApplicationsController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getUser(); 8 + $application = $request->getURIData('application'); 9 + 10 + $application = id(new PhabricatorApplicationQuery()) 11 + ->setViewer($viewer) 12 + ->withClasses(array($application)) 13 + ->requireCapabilities( 14 + array( 15 + PhabricatorPolicyCapability::CAN_VIEW, 16 + PhabricatorPolicyCapability::CAN_EDIT, 17 + )) 18 + ->executeOne(); 19 + if (!$application) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + $title = $application->getName(); 24 + 25 + $uri = $request->getRequestURI(); 26 + $uri->setQueryParams(array()); 27 + 28 + $new = $request->getStr('new'); 29 + $edit = $request->getInt('edit'); 30 + $delete = $request->getInt('delete'); 31 + 32 + if ($new) { 33 + return $this->returnNewAddressResponse($request, $uri, $application); 34 + } 35 + 36 + if ($edit) { 37 + return $this->returnEditAddressResponse($request, $uri, $edit); 38 + } 39 + 40 + if ($delete) { 41 + return $this->returnDeleteAddressResponse($request, $uri, $delete); 42 + } 43 + 44 + $emails = id(new PhabricatorMetaMTAApplicationEmailQuery()) 45 + ->setViewer($viewer) 46 + ->withApplicationPHIDs(array($application->getPHID())) 47 + ->execute(); 48 + 49 + $highlight = $request->getInt('highlight'); 50 + $rowc = array(); 51 + $rows = array(); 52 + foreach ($emails as $email) { 53 + 54 + $button_edit = javelin_tag( 55 + 'a', 56 + array( 57 + 'class' => 'button small grey', 58 + 'href' => $uri->alter('edit', $email->getID()), 59 + 'sigil' => 'workflow', 60 + ), 61 + pht('Edit')); 62 + 63 + $button_remove = javelin_tag( 64 + 'a', 65 + array( 66 + 'class' => 'button small grey', 67 + 'href' => $uri->alter('delete', $email->getID()), 68 + 'sigil' => 'workflow', 69 + ), 70 + pht('Delete')); 71 + 72 + if ($highlight == $email->getID()) { 73 + $rowc[] = 'highlighted'; 74 + } else { 75 + $rowc[] = null; 76 + } 77 + 78 + $rows[] = array( 79 + $email->getAddress(), 80 + $button_edit, 81 + $button_remove, 82 + ); 83 + } 84 + 85 + $table = id(new AphrontTableView($rows)) 86 + ->setNoDataString(pht('No application emails created yet.')); 87 + $table->setHeaders( 88 + array( 89 + pht('Email'), 90 + pht('Edit'), 91 + pht('Delete'), 92 + )); 93 + $table->setColumnClasses( 94 + array( 95 + 'wide', 96 + 'action', 97 + 'action', 98 + )); 99 + $table->setRowClasses($rowc); 100 + $table->setColumnVisibility( 101 + array( 102 + true, 103 + true, 104 + true, 105 + )); 106 + $form = id(new AphrontFormView()) 107 + ->setUser($viewer); 108 + 109 + $view_uri = $this->getApplicationURI('view/'.get_class($application).'/'); 110 + $crumbs = $this->buildApplicationCrumbs(); 111 + $crumbs->addTextCrumb($application->getName(), $view_uri); 112 + $crumbs->addTextCrumb(pht('Edit Application Emails')); 113 + 114 + $header = id(new PHUIHeaderView()) 115 + ->setHeader(pht('Edit Application Emails: %s', $application->getName())); 116 + 117 + $icon = id(new PHUIIconView()) 118 + ->setIconFont('fa-plus'); 119 + $button = new PHUIButtonView(); 120 + $button->setText(pht('Add New Address')); 121 + $button->setTag('a'); 122 + $button->setHref($uri->alter('new', 'true')); 123 + $button->setIcon($icon); 124 + $button->addSigil('workflow'); 125 + $header->addActionLink($button); 126 + 127 + $object_box = id(new PHUIObjectBoxView()) 128 + ->setHeader($header) 129 + ->appendChild($table) 130 + ->appendChild( 131 + id(new PHUIBoxView()) 132 + ->appendChild($application->getAppEmailBlurb()) 133 + ->addPadding(PHUI::PADDING_MEDIUM)); 134 + 135 + return $this->buildApplicationPage( 136 + array( 137 + $crumbs, 138 + $object_box, 139 + ), 140 + array( 141 + 'title' => $title, 142 + )); 143 + } 144 + 145 + private function validateApplicationEmail($email) { 146 + $errors = array(); 147 + $e_email = true; 148 + 149 + if (!strlen($email)) { 150 + $e_email = pht('Required'); 151 + $errors[] = pht('Email is required.'); 152 + } else if (!PhabricatorUserEmail::isValidAddress($email)) { 153 + $e_email = pht('Invalid'); 154 + $errors[] = PhabricatorUserEmail::describeValidAddresses(); 155 + } else if (!PhabricatorUserEmail::isAllowedAddress($email)) { 156 + $e_email = pht('Disallowed'); 157 + $errors[] = PhabricatorUserEmail::describeAllowedAddresses(); 158 + } 159 + $user_emails = id(new PhabricatorUserEmail()) 160 + ->loadAllWhere('address = %s', $email); 161 + if ($user_emails) { 162 + $e_email = pht('Duplicate'); 163 + $errors[] = pht('A user already has this email.'); 164 + } 165 + 166 + return array($e_email, $errors); 167 + } 168 + 169 + private function returnNewAddressResponse( 170 + AphrontRequest $request, 171 + PhutilURI $uri, 172 + PhabricatorApplication $application) { 173 + 174 + $viewer = $request->getUser(); 175 + $email_object = 176 + PhabricatorMetaMTAApplicationEmail::initializeNewAppEmail($viewer) 177 + ->setApplicationPHID($application->getPHID()); 178 + 179 + return $this->returnSaveAddressResponse( 180 + $request, 181 + $uri, 182 + $email_object, 183 + $is_new = true); 184 + } 185 + 186 + private function returnEditAddressResponse( 187 + AphrontRequest $request, 188 + PhutilURI $uri, 189 + $email_object_id) { 190 + 191 + $viewer = $request->getUser(); 192 + $email_object = id(new PhabricatorMetaMTAApplicationEmailQuery()) 193 + ->setViewer($viewer) 194 + ->withIDs(array($email_object_id)) 195 + ->requireCapabilities( 196 + array( 197 + PhabricatorPolicyCapability::CAN_VIEW, 198 + PhabricatorPolicyCapability::CAN_EDIT, 199 + )) 200 + ->executeOne(); 201 + if (!$email_object) { 202 + return new Aphront404Response(); 203 + } 204 + 205 + return $this->returnSaveAddressResponse( 206 + $request, 207 + $uri, 208 + $email_object, 209 + $is_new = false); 210 + } 211 + 212 + private function returnSaveAddressResponse( 213 + AphrontRequest $request, 214 + PhutilURI $uri, 215 + PhabricatorMetaMTAApplicationEmail $email_object, 216 + $is_new) { 217 + 218 + $viewer = $request->getUser(); 219 + 220 + $e_email = true; 221 + $email = null; 222 + $errors = array(); 223 + if ($request->isDialogFormPost()) { 224 + $email = trim($request->getStr('email')); 225 + list($e_email, $errors) = $this->validateApplicationEmail($email); 226 + $email_object->setAddress($email); 227 + 228 + if (!$errors) { 229 + try { 230 + $email_object->save(); 231 + return id(new AphrontRedirectResponse())->setURI( 232 + $uri->alter('highlight', $email_object->getID())); 233 + } catch (AphrontDuplicateKeyQueryException $ex) { 234 + $e_email = pht('Duplicate'); 235 + $errors[] = pht( 236 + 'Another application is already configured to use this email '. 237 + 'address.'); 238 + } 239 + } 240 + } 241 + 242 + if ($errors) { 243 + $errors = id(new AphrontErrorView()) 244 + ->setErrors($errors); 245 + } 246 + 247 + $form = id(new PHUIFormLayoutView()) 248 + ->appendChild( 249 + id(new AphrontFormTextControl()) 250 + ->setLabel(pht('Email')) 251 + ->setName('email') 252 + ->setValue($email_object->getAddress()) 253 + ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) 254 + ->setError($e_email)); 255 + 256 + $dialog = id(new AphrontDialogView()) 257 + ->setUser($viewer) 258 + ->setTitle(pht('New Address')) 259 + ->appendChild($errors) 260 + ->appendChild($form) 261 + ->addSubmitButton(pht('Save')) 262 + ->addCancelButton($uri); 263 + 264 + if ($is_new) { 265 + $dialog->addHiddenInput('new', 'true'); 266 + } 267 + 268 + return id(new AphrontDialogResponse())->setDialog($dialog); 269 + } 270 + 271 + private function returnDeleteAddressResponse( 272 + AphrontRequest $request, 273 + PhutilURI $uri, 274 + $email_object_id) { 275 + 276 + $viewer = $request->getUser(); 277 + $email_object = id(new PhabricatorMetaMTAApplicationEmailQuery()) 278 + ->setViewer($viewer) 279 + ->withIDs(array($email_object_id)) 280 + ->requireCapabilities( 281 + array( 282 + PhabricatorPolicyCapability::CAN_VIEW, 283 + PhabricatorPolicyCapability::CAN_EDIT, 284 + )) 285 + ->executeOne(); 286 + if (!$email_object) { 287 + return new Aphront404Response(); 288 + } 289 + 290 + if ($request->isDialogFormPost()) { 291 + $email_object->delete(); 292 + return id(new AphrontRedirectResponse())->setURI($uri); 293 + } 294 + 295 + $dialog = id(new AphrontDialogView()) 296 + ->setUser($viewer) 297 + ->addHiddenInput('delete', $email_object_id) 298 + ->setTitle(pht('Delete Address')) 299 + ->appendParagraph(pht( 300 + 'Are you sure you want to delete this email address?')) 301 + ->addSubmitButton(pht('Delete')) 302 + ->addCancelButton($uri); 303 + 304 + return id(new AphrontDialogResponse())->setDialog($dialog); 305 + } 306 + 307 + }
+116
src/applications/metamta/query/PhabricatorMetaMTAApplicationEmailQuery.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAApplicationEmailQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $addresses; 9 + private $applicationPHIDs; 10 + 11 + public function withIDs(array $ids) { 12 + $this->ids = $ids; 13 + return $this; 14 + } 15 + 16 + public function withPHIDs(array $phids) { 17 + $this->phids = $phids; 18 + return $this; 19 + } 20 + 21 + public function withAddresses(array $addresses) { 22 + $this->addresses = $addresses; 23 + return $this; 24 + } 25 + 26 + public function withApplicationPHIDs(array $phids) { 27 + $this->applicationPHIDs = $phids; 28 + return $this; 29 + } 30 + 31 + protected function loadPage() { 32 + $table = new PhabricatorMetaMTAApplicationEmail(); 33 + $conn_r = $table->establishConnection('r'); 34 + 35 + $data = queryfx_all( 36 + $conn_r, 37 + 'SELECT * FROM %T appemail %Q %Q %Q %Q', 38 + $table->getTableName(), 39 + $this->buildWhereClause($conn_r), 40 + $this->buildApplicationSearchGroupClause($conn_r), 41 + $this->buildOrderClause($conn_r), 42 + $this->buildLimitClause($conn_r)); 43 + 44 + return $table->loadAllFromArray($data); 45 + } 46 + 47 + protected function willFilterPage(array $app_emails) { 48 + $app_emails_map = mgroup($app_emails, 'getApplicationPHID'); 49 + $applications = id(new PhabricatorApplicationQuery()) 50 + ->setViewer($this->getViewer()) 51 + ->withPHIDs(array_keys($app_emails_map)) 52 + ->execute(); 53 + $applications = mpull($applications, null, 'getPHID'); 54 + 55 + foreach ($app_emails_map as $app_phid => $app_emails_group) { 56 + foreach ($app_emails_group as $app_email) { 57 + $application = idx($applications, $app_phid); 58 + if (!$application) { 59 + unset($app_emails[$app_phid]); 60 + continue; 61 + } 62 + $app_email->attachApplication($application); 63 + } 64 + } 65 + return $app_emails; 66 + } 67 + 68 + private function buildWhereClause($conn_r) { 69 + $where = array(); 70 + 71 + if ($this->addresses !== null) { 72 + $where[] = qsprintf( 73 + $conn_r, 74 + 'appemail.address IN (%Ls)', 75 + $this->addresses); 76 + } 77 + 78 + if ($this->applicationPHIDs !== null) { 79 + $where[] = qsprintf( 80 + $conn_r, 81 + 'appemail.applicationPHID IN (%Ls)', 82 + $this->applicationPHIDs); 83 + } 84 + 85 + if ($this->phids !== null) { 86 + $where[] = qsprintf( 87 + $conn_r, 88 + 'appemail.phid IN (%Ls)', 89 + $this->phids); 90 + } 91 + 92 + if ($this->ids !== null) { 93 + $where[] = qsprintf( 94 + $conn_r, 95 + 'appemail.id IN (%Ld)', 96 + $this->ids); 97 + } 98 + 99 + $where[] = $this->buildPagingClause($conn_r); 100 + 101 + return $this->formatWhereClause($where); 102 + } 103 + 104 + protected function getPagingColumn() { 105 + return 'appemail.id'; 106 + } 107 + 108 + protected function getApplicationSearchObjectPHIDColumn() { 109 + return 'appemail.phid'; 110 + } 111 + 112 + public function getQueryApplicationClass() { 113 + return 'PhabricatorMetaMTAApplication'; 114 + } 115 + 116 + }
+5 -1
src/applications/metamta/receiver/PhabricatorMailReceiver.php
··· 15 15 $this->processReceivedMail($mail, $sender); 16 16 } 17 17 18 + public function getViewer() { 19 + return PhabricatorUser::getOmnipotentUser(); 20 + } 21 + 18 22 public function validateSender( 19 23 PhabricatorMetaMTAReceivedMail $mail, 20 24 PhabricatorUser $sender) { ··· 103 107 if ($allow_email_users) { 104 108 $from_obj = new PhutilEmailAddress($from); 105 109 $xuser = id(new PhabricatorExternalAccountQuery()) 106 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 110 + ->setViewer($this->getViewer()) 107 111 ->withAccountTypes(array('email')) 108 112 ->withAccountDomains(array($from_obj->getDomainName(), 'self')) 109 113 ->withAccountIDs(array($from_obj->getAddress()))
+80
src/applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAApplicationEmail 4 + extends PhabricatorMetaMTADAO 5 + implements PhabricatorPolicyInterface { 6 + 7 + protected $applicationPHID; 8 + protected $address; 9 + protected $configData; 10 + 11 + private $application = self::ATTACHABLE; 12 + 13 + protected function getConfiguration() { 14 + return array( 15 + self::CONFIG_AUX_PHID => true, 16 + self::CONFIG_SERIALIZATION => array( 17 + 'configData' => self::SERIALIZATION_JSON, 18 + ), 19 + self::CONFIG_COLUMN_SCHEMA => array( 20 + 'address' => 'sort128', 21 + ), 22 + self::CONFIG_KEY_SCHEMA => array( 23 + 'key_address' => array( 24 + 'columns' => array('address'), 25 + 'unique' => true, 26 + ), 27 + 'key_application' => array( 28 + 'columns' => array('applicationPHID'), 29 + ), 30 + ), 31 + ) + parent::getConfiguration(); 32 + } 33 + 34 + public function generatePHID() { 35 + return PhabricatorPHID::generateNewPHID( 36 + PhabricatorMetaMTAApplicationEmailPHIDType::TYPECONST); 37 + } 38 + 39 + public static function initializeNewAppEmail(PhabricatorUser $actor) { 40 + return id(new PhabricatorMetaMTAApplicationEmail()) 41 + ->setConfigData(array()); 42 + } 43 + 44 + public function attachApplication(PhabricatorApplication $app) { 45 + $this->application = $app; 46 + return $this; 47 + } 48 + 49 + public function getApplication() { 50 + return self::assertAttached($this->application); 51 + } 52 + 53 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 54 + 55 + 56 + public function getCapabilities() { 57 + return array( 58 + PhabricatorPolicyCapability::CAN_VIEW, 59 + PhabricatorPolicyCapability::CAN_EDIT, 60 + ); 61 + } 62 + 63 + public function getPolicy($capability) { 64 + return $this->getApplication()->getPolicy($capability); 65 + } 66 + 67 + public function hasAutomaticCapability( 68 + $capability, 69 + PhabricatorUser $viewer) { 70 + 71 + return $this->getApplication()->hasAutomaticCapability( 72 + $capability, 73 + $viewer); 74 + } 75 + 76 + public function describeAutomaticCapability($capability) { 77 + return $this->getApplication()->describeAutomaticCapability($capability); 78 + } 79 + 80 + }
+14 -7
src/applications/metamta/storage/__tests__/PhabricatorMetaMTAReceivedMailTestCase.php
··· 64 64 } 65 65 66 66 public function testDropUnknownSenderMail() { 67 + $this->setManiphestCreateEmail(); 68 + 67 69 $env = PhabricatorEnv::beginScopedEnv(); 68 - $env->overrideEnvConfig( 69 - 'metamta.maniphest.public-create-email', 70 - 'bugs@example.com'); 71 70 $env->overrideEnvConfig('phabricator.allow-email-users', false); 72 71 $env->overrideEnvConfig('metamta.maniphest.default-public-author', null); 73 72 ··· 89 88 90 89 91 90 public function testDropDisabledSenderMail() { 92 - $env = PhabricatorEnv::beginScopedEnv(); 93 - $env->overrideEnvConfig( 94 - 'metamta.maniphest.public-create-email', 95 - 'bugs@example.com'); 91 + $this->setManiphestCreateEmail(); 96 92 97 93 $user = $this->generateNewTestUser() 98 94 ->setIsDisabled(true) ··· 112 108 $this->assertEqual( 113 109 MetaMTAReceivedMailStatus::STATUS_DISABLED_SENDER, 114 110 $mail->getStatus()); 111 + } 112 + 113 + private function setManiphestCreateEmail() { 114 + $maniphest_app = new PhabricatorManiphestApplication(); 115 + try { 116 + id(new PhabricatorMetaMTAApplicationEmail()) 117 + ->setApplicationPHID($maniphest_app->getPHID()) 118 + ->setAddress('bugs@example.com') 119 + ->setConfigData(array()) 120 + ->save(); 121 + } catch (AphrontDuplicateKeyQueryException $ex) {} 115 122 } 116 123 117 124 }
+44
src/applications/phid/PhabricatorMetaMTAApplicationEmailPHIDType.php
··· 1 + <?php 2 + 3 + final class PhabricatorMetaMTAApplicationEmailPHIDType 4 + extends PhabricatorPHIDType { 5 + 6 + const TYPECONST = 'APPE'; 7 + 8 + public function getTypeName() { 9 + return pht('Application Email'); 10 + } 11 + 12 + public function getPHIDTypeApplicationClass() { 13 + return 'PhabricatorMetaMTAApplication'; 14 + } 15 + 16 + public function getTypeIcon() { 17 + return 'fa-email bluegrey'; 18 + } 19 + 20 + public function newObject() { 21 + return new PhabricatorMetaMTAApplicationEmail(); 22 + } 23 + 24 + protected function buildQueryForObjects( 25 + PhabricatorObjectQuery $query, 26 + array $phids) { 27 + 28 + return id(new PhabricatorMetaMTAApplicationEmailQuery()) 29 + ->withPHIDs($phids); 30 + } 31 + 32 + public function loadHandles( 33 + PhabricatorHandleQuery $query, 34 + array $handles, 35 + array $objects) { 36 + 37 + foreach ($handles as $phid => $handle) { 38 + $email = $objects[$phid]; 39 + 40 + $handle->setName($email->getAddress()); 41 + $handle->setFullName($email->getAddress()); 42 + } 43 + } 44 + }