mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
Projects - add mail to project updates
Summary: ...which lets all the fancy settings for Email | Notify | Off be possible. Fixes T8164. Wasn't too sure the best way to break things up but members vs watchers felt meaningful to break out to me. Also fixes a small bug where we were generating bad slug updated stories by messing with the signature of the slug data. Perhaps this fix isn't even good enough (the array_keys()) call and instead we'll need to implement transaction has effect and do a sort? Test Plan: used ./bin/mail list-outbound and ./bin/mail show-outbound --id XX to verify reasonable emails were being generated. saw new preferences in settings. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8164 Differential Revision: https://secure.phabricator.com/D12868
This commit is contained in:
parent
3ef0721ada
commit
fa82c17079
5 changed files with 145 additions and 6 deletions
|
@ -3101,6 +3101,7 @@ phutil_register_library_map(array(
|
||||||
'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php',
|
'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php',
|
||||||
'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
|
'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php',
|
||||||
'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php',
|
'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php',
|
||||||
|
'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php',
|
||||||
'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
|
'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
|
||||||
'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
|
'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
|
||||||
'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php',
|
'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php',
|
||||||
|
@ -6670,6 +6671,7 @@ phutil_register_library_map(array(
|
||||||
'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod',
|
'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod',
|
||||||
'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule',
|
||||||
'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase',
|
'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase',
|
||||||
|
'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
|
||||||
'QueryFormattingTestCase' => 'PhabricatorTestCase',
|
'QueryFormattingTestCase' => 'PhabricatorTestCase',
|
||||||
'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
|
'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
|
||||||
'ReleephBranch' => array(
|
'ReleephBranch' => array(
|
||||||
|
|
|
@ -41,7 +41,7 @@ final class PhabricatorProjectTransactionEditor
|
||||||
$slugs = $object->getSlugs();
|
$slugs = $object->getSlugs();
|
||||||
$slugs = mpull($slugs, 'getSlug', 'getSlug');
|
$slugs = mpull($slugs, 'getSlug', 'getSlug');
|
||||||
unset($slugs[$object->getPrimarySlug()]);
|
unset($slugs[$object->getPrimarySlug()]);
|
||||||
return $slugs;
|
return array_keys($slugs);
|
||||||
case PhabricatorProjectTransaction::TYPE_STATUS:
|
case PhabricatorProjectTransaction::TYPE_STATUS:
|
||||||
return $object->getStatus();
|
return $object->getStatus();
|
||||||
case PhabricatorProjectTransaction::TYPE_IMAGE:
|
case PhabricatorProjectTransaction::TYPE_IMAGE:
|
||||||
|
@ -403,11 +403,74 @@ final class PhabricatorProjectTransactionEditor
|
||||||
return parent::requireCapabilities($object, $xaction);
|
return parent::requireCapabilities($object, $xaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function loadEdges(
|
||||||
* Note: this is implemented for Feed purposes.
|
PhabricatorLiskDAO $object,
|
||||||
*/
|
array $xactions) {
|
||||||
|
|
||||||
|
$member_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
|
||||||
|
$object->getPHID(),
|
||||||
|
PhabricatorProjectProjectHasMemberEdgeType::EDGECONST);
|
||||||
|
$object->attachMemberPHIDs($member_phids);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function shouldSendMail(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
array $xactions) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getMailSubjectPrefix() {
|
||||||
|
return pht('[Project]');
|
||||||
|
}
|
||||||
|
|
||||||
protected function getMailTo(PhabricatorLiskDAO $object) {
|
protected function getMailTo(PhabricatorLiskDAO $object) {
|
||||||
return array();
|
return $object->getMemberPHIDs();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getMailCC(PhabricatorLiskDAO $object) {
|
||||||
|
$all = parent::getMailCC($object);
|
||||||
|
return array_diff($all, $object->getMemberPHIDs());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMailTagsMap() {
|
||||||
|
return array(
|
||||||
|
PhabricatorProjectTransaction::MAILTAG_METADATA =>
|
||||||
|
pht('Project name, hashtags, icon, image, or color changes.'),
|
||||||
|
PhabricatorProjectTransaction::MAILTAG_MEMBERS =>
|
||||||
|
pht('Project membership changes.'),
|
||||||
|
PhabricatorProjectTransaction::MAILTAG_WATCHERS =>
|
||||||
|
pht('Project watcher list changes.'),
|
||||||
|
PhabricatorProjectTransaction::MAILTAG_OTHER =>
|
||||||
|
pht('Other project activity not listed above occurs.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
|
||||||
|
return id(new ProjectReplyHandler())
|
||||||
|
->setMailReceiver($object);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
|
||||||
|
$id = $object->getID();
|
||||||
|
$name = $object->getName();
|
||||||
|
|
||||||
|
return id(new PhabricatorMetaMTAMail())
|
||||||
|
->setSubject("{$name}")
|
||||||
|
->addHeader('Thread-Topic', "Project {$id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildMailBody(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
array $xactions) {
|
||||||
|
|
||||||
|
$body = parent::buildMailBody($object, $xactions);
|
||||||
|
|
||||||
|
$uri = '/project/profile/'.$object->getID().'/';
|
||||||
|
$body->addLinkSection(
|
||||||
|
pht('PROJECT DETAIL'),
|
||||||
|
PhabricatorEnv::getProductionURI($uri));
|
||||||
|
|
||||||
|
return $body;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function shouldPublishFeedStory(
|
protected function shouldPublishFeedStory(
|
||||||
|
|
20
src/applications/project/mail/ProjectReplyHandler.php
Normal file
20
src/applications/project/mail/ProjectReplyHandler.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ProjectReplyHandler
|
||||||
|
extends PhabricatorApplicationTransactionReplyHandler {
|
||||||
|
|
||||||
|
public function validateMailReceiver($mail_receiver) {
|
||||||
|
if (!($mail_receiver instanceof PhabricatorProject)) {
|
||||||
|
throw new Exception('Mail receiver is not a PhabricatorProject.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getObjectPrefix() {
|
||||||
|
return PhabricatorProjectProjectPHIDType::TYPECONST;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function shouldCreateCommentFromMailBody() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,6 +14,11 @@ final class PhabricatorProjectTransaction
|
||||||
// NOTE: This is deprecated, members are just a normal edge now.
|
// NOTE: This is deprecated, members are just a normal edge now.
|
||||||
const TYPE_MEMBERS = 'project:members';
|
const TYPE_MEMBERS = 'project:members';
|
||||||
|
|
||||||
|
const MAILTAG_METADATA = 'project-metadata';
|
||||||
|
const MAILTAG_MEMBERS = 'project-members';
|
||||||
|
const MAILTAG_WATCHERS = 'project-watchers';
|
||||||
|
const MAILTAG_OTHER = 'project-other';
|
||||||
|
|
||||||
public function getApplicationName() {
|
public function getApplicationName() {
|
||||||
return 'project';
|
return 'project';
|
||||||
}
|
}
|
||||||
|
@ -106,6 +111,8 @@ final class PhabricatorProjectTransaction
|
||||||
$old,
|
$old,
|
||||||
$new);
|
$new);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case self::TYPE_STATUS:
|
case self::TYPE_STATUS:
|
||||||
if ($old == 0) {
|
if ($old == 0) {
|
||||||
return pht(
|
return pht(
|
||||||
|
@ -116,6 +123,8 @@ final class PhabricatorProjectTransaction
|
||||||
'%s activated this project.',
|
'%s activated this project.',
|
||||||
$author_handle);
|
$author_handle);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case self::TYPE_IMAGE:
|
case self::TYPE_IMAGE:
|
||||||
// TODO: Some day, it would be nice to show the images.
|
// TODO: Some day, it would be nice to show the images.
|
||||||
if (!$old) {
|
if (!$old) {
|
||||||
|
@ -134,18 +143,21 @@ final class PhabricatorProjectTransaction
|
||||||
$this->renderHandleLink($old),
|
$this->renderHandleLink($old),
|
||||||
$this->renderHandleLink($new));
|
$this->renderHandleLink($new));
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case self::TYPE_ICON:
|
case self::TYPE_ICON:
|
||||||
return pht(
|
return pht(
|
||||||
'%s set this project\'s icon to %s.',
|
'%s set this project\'s icon to %s.',
|
||||||
$author_handle,
|
$author_handle,
|
||||||
PhabricatorProjectIcon::getLabel($new));
|
PhabricatorProjectIcon::getLabel($new));
|
||||||
|
break;
|
||||||
|
|
||||||
case self::TYPE_COLOR:
|
case self::TYPE_COLOR:
|
||||||
return pht(
|
return pht(
|
||||||
'%s set this project\'s color to %s.',
|
'%s set this project\'s color to %s.',
|
||||||
$author_handle,
|
$author_handle,
|
||||||
PHUITagView::getShadeName($new));
|
PHUITagView::getShadeName($new));
|
||||||
|
break;
|
||||||
|
|
||||||
case self::TYPE_LOCKED:
|
case self::TYPE_LOCKED:
|
||||||
if ($new) {
|
if ($new) {
|
||||||
|
@ -157,6 +169,7 @@ final class PhabricatorProjectTransaction
|
||||||
'%s unlocked this project\'s membership.',
|
'%s unlocked this project\'s membership.',
|
||||||
$author_handle);
|
$author_handle);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case self::TYPE_SLUGS:
|
case self::TYPE_SLUGS:
|
||||||
$add = array_diff($new, $old);
|
$add = array_diff($new, $old);
|
||||||
|
@ -183,6 +196,7 @@ final class PhabricatorProjectTransaction
|
||||||
count($rem),
|
count($rem),
|
||||||
$this->renderSlugList($rem));
|
$this->renderSlugList($rem));
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case self::TYPE_MEMBERS:
|
case self::TYPE_MEMBERS:
|
||||||
$add = array_diff($new, $old);
|
$add = array_diff($new, $old);
|
||||||
|
@ -221,6 +235,7 @@ final class PhabricatorProjectTransaction
|
||||||
$this->renderHandleList($rem));
|
$this->renderHandleList($rem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getTitle();
|
return parent::getTitle();
|
||||||
|
@ -339,12 +354,43 @@ final class PhabricatorProjectTransaction
|
||||||
$object_handle,
|
$object_handle,
|
||||||
$this->renderSlugList($rem));
|
$this->renderSlugList($rem));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::getTitleForFeed();
|
return parent::getTitleForFeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMailTags() {
|
||||||
|
$tags = array();
|
||||||
|
switch ($this->getTransactionType()) {
|
||||||
|
case self::TYPE_NAME:
|
||||||
|
case self::TYPE_SLUGS:
|
||||||
|
case self::TYPE_IMAGE:
|
||||||
|
case self::TYPE_ICON:
|
||||||
|
case self::TYPE_COLOR:
|
||||||
|
$tags[] = self::MAILTAG_METADATA;
|
||||||
|
break;
|
||||||
|
case PhabricatorTransactions::TYPE_EDGE:
|
||||||
|
$type = $this->getMetadata('edge:type');
|
||||||
|
$type = head($type);
|
||||||
|
$type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST;
|
||||||
|
$type_watcher = PhabricatorObjectHasWatcherEdgeType::EDGECONST;
|
||||||
|
if ($type == $type_member) {
|
||||||
|
$tags[] = self::MAILTAG_MEMBERS;
|
||||||
|
} else if ($type == $type_watcher) {
|
||||||
|
$tags[] = self::MAILTAG_WATCHERS;
|
||||||
|
} else {
|
||||||
|
$tags[] = self::MAILTAG_OTHER;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case self::TYPE_STATUS:
|
||||||
|
case self::TYPE_LOCKED:
|
||||||
|
default:
|
||||||
|
$tags[] = self::MAILTAG_OTHER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $tags;
|
||||||
|
}
|
||||||
|
|
||||||
private function renderSlugList($slugs) {
|
private function renderSlugList($slugs) {
|
||||||
return implode(', ', $slugs);
|
return implode(', ', $slugs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -840,6 +840,8 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
// subscribers to pick up changes caused by Herald (or by other side effects
|
// subscribers to pick up changes caused by Herald (or by other side effects
|
||||||
// in various transaction phases).
|
// in various transaction phases).
|
||||||
$this->loadSubscribers($object);
|
$this->loadSubscribers($object);
|
||||||
|
// Hook for other edges that may need (re-)loading
|
||||||
|
$this->loadEdges($object, $xactions);
|
||||||
|
|
||||||
$this->loadHandles($xactions);
|
$this->loadHandles($xactions);
|
||||||
|
|
||||||
|
@ -965,6 +967,12 @@ abstract class PhabricatorApplicationTransactionEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function loadEdges(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
array $xactions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
private function validateEditParameters(
|
private function validateEditParameters(
|
||||||
PhabricatorLiskDAO $object,
|
PhabricatorLiskDAO $object,
|
||||||
array $xactions) {
|
array $xactions) {
|
||||||
|
|
Loading…
Reference in a new issue