diff --git a/resources/sql/autopatches/20150501.calendar.1.reply.sql b/resources/sql/autopatches/20150501.calendar.1.reply.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150501.calendar.1.reply.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_calendar.calendar_event + ADD mailKey binary(20) NOT NULL; diff --git a/resources/sql/autopatches/20150501.calendar.2.reply.php b/resources/sql/autopatches/20150501.calendar.2.reply.php new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150501.calendar.2.reply.php @@ -0,0 +1,21 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $event) { + $id = $event->getID(); + + echo "Populating event {$id}...\n"; + + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $table->getTableName(), + Filesystem::readRandomCharacters(20), + $id); +} + +echo "Done.\n"; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1491,6 +1491,7 @@ 'PhabricatorCalendarEventInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php', 'PhabricatorCalendarEventJoinController' => 'applications/calendar/controller/PhabricatorCalendarEventJoinController.php', 'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php', + 'PhabricatorCalendarEventMailReceiver' => 'applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php', 'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php', 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', 'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php', @@ -1502,6 +1503,7 @@ 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', + 'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php', 'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php', 'PhabricatorCalendarViewController' => 'applications/calendar/controller/PhabricatorCalendarViewController.php', 'PhabricatorCampfireProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php', @@ -4830,6 +4832,7 @@ 'PhabricatorCalendarEventInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarEventJoinController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType', 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine', @@ -4841,6 +4844,7 @@ 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', + 'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorCalendarViewController' => 'PhabricatorCalendarController', 'PhabricatorCampfireProtocolAdapter' => 'PhabricatorBotBaseStreamingProtocolAdapter', diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -74,4 +74,18 @@ return $items; } + public function getMailCommandObjects() { + return array( + 'event' => array( + 'name' => pht('Email Commands: Events'), + 'header' => pht('Interacting with Calendar Events'), + 'object' => new PhabricatorCalendarEvent(), + 'summary' => pht( + 'This page documents the commands you can use to interact with '. + 'events in Calendar. These commands work when creating new tasks '. + 'via email and when replying to existing tasks.'), + ), + ); + } + } diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -209,10 +209,6 @@ return $errors; } - protected function getMailTo(PhabricatorLiskDAO $object) { - return array($object->getUserPHID()); - } - protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { @@ -222,4 +218,86 @@ protected function supportsSearch() { return true; } + + protected function shouldSendMail( + PhabricatorLiskDAO $object, + array $xactions) { + + $xactions = mfilter($xactions, 'shouldHide', true); + return $xactions; + } + + protected function getMailSubjectPrefix() { + return pht('[Calendar]'); + } + + protected function getMailTo(PhabricatorLiskDAO $object) { + $phids = array(); + + if ($object->getUserPHID()) { + $phids[] = $object->getUserPHID(); + } + $phids[] = $this->getActingAsPHID(); + + $invitees = $object->getInvitees(); + foreach ($invitees as $phid => $status) { + if ($status === PhabricatorCalendarEventInvitee::STATUS_ATTENDING + || $status === PhabricatorCalendarEventInvitee::STATUS_INVITED) { + $phids[] = $phid; + } + } + + $phids = array_unique($phids); + return $phids; + } + + public function getMailTagsMap() { + return array( + PhabricatorCalendarEventTransaction::MAILTAG_CONTENT => + pht("An event's name, status, invite list, and description + changes."), + PhabricatorCalendarEventTransaction::MAILTAG_RESCHEDULE => + pht("An event's start and end date and cancellation status + changes."), + PhabricatorCalendarEventTransaction::MAILTAG_OTHER => + pht('Other event activity not listed above occurs.'), + ); + } + + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new PhabricatorCalendarReplyHandler()) + ->setMailReceiver($object); + } + + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $id = $object->getID(); + $name = $object->getName(); + + return id(new PhabricatorMetaMTAMail()) + ->setSubject("E{$id}: {$name}") + ->addHeader('Thread-Topic', "E{$id}: ".$object->getName()); + } + + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $description = $object->getDescription(); + $body = parent::buildMailBody($object, $xactions); + + if (strlen($description)) { + $body->addTextSection( + pht('EVENT DESCRIPTION'), + $object->getDescription()); + } + + $body->addLinkSection( + pht('EVENT DETAIL'), + PhabricatorEnv::getProductionURI('/E'.$object->getID())); + + + return $body; + } + + } diff --git a/src/applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php b/src/applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php @@ -0,0 +1,28 @@ +setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + } + + protected function getTransactionReplyHandler() { + return new PhabricatorCalendarReplyHandler(); + } + +} diff --git a/src/applications/calendar/mail/PhabricatorCalendarReplyHandler.php b/src/applications/calendar/mail/PhabricatorCalendarReplyHandler.php new file mode 100644 --- /dev/null +++ b/src/applications/calendar/mail/PhabricatorCalendarReplyHandler.php @@ -0,0 +1,15 @@ +attachInvitees(array()); } + public function save() { + if ($this->getDateTo() <= $this->getDateFrom()) { + throw new PhabricatorCalendarEventInvalidEpochException(); + } + + if (!$this->mailKey) { + $this->mailKey = Filesystem::readRandomCharacters(20); + } + return parent::save(); + } + private static $statusTexts = array( self::STATUS_AWAY => 'away', self::STATUS_SPORADIC => 'sporadic', @@ -76,6 +88,7 @@ 'status' => 'uint32', 'description' => 'text', 'isCancelled' => 'bool', + 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'userPHID_dateFrom' => array( @@ -156,19 +169,6 @@ return $is_attending; } - /** - * Validates data and throws exceptions for non-sensical status - * windows - */ - public function save() { - - if ($this->getDateTo() <= $this->getDateFrom()) { - throw new PhabricatorCalendarEventInvalidEpochException(); - } - - return parent::save(); - } - /* -( Markup Interface )--------------------------------------------------- */ diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -11,6 +11,8 @@ const TYPE_CANCEL = 'calendar.cancel'; const TYPE_INVITE = 'calendar.invite'; + + const MAILTAG_RESCHEDULE = 'calendar-reschedule'; const MAILTAG_CONTENT = 'calendar-content'; const MAILTAG_OTHER = 'calendar-other'; @@ -442,25 +444,15 @@ $tags = array(); switch ($this->getTransactionType()) { case self::TYPE_NAME: - $tags[] = self::MAILTAG_CONTENT; - break; - case self::TYPE_START_DATE: - $tags[] = self::MAILTAG_CONTENT; - break; - case self::TYPE_END_DATE: - $tags[] = self::MAILTAG_CONTENT; - break; case self::TYPE_STATUS: - $tags[] = self::MAILTAG_OTHER; - break; case self::TYPE_DESCRIPTION: + case self::TYPE_INVITE: $tags[] = self::MAILTAG_CONTENT; break; + case self::TYPE_START_DATE: + case self::TYPE_END_DATE: case self::TYPE_CANCEL: - $tags[] = self::MAILTAG_CONTENT; - break; - case self::TYPE_INVITE: - $tags[] = self::MAILTAG_CONTENT; + $tags[] = self::MAILTAG_RESCHEDULE; break; } return $tags;