diff --git a/resources/sql/autopatches/20150429.calendar.1.invitee.sql b/resources/sql/autopatches/20150429.calendar.1.invitee.sql new file mode 100644 index 0000000000..b39294b670 --- /dev/null +++ b/resources/sql/autopatches/20150429.calendar.1.invitee.sql @@ -0,0 +1,11 @@ +CREATE TABLE {$NAMESPACE}_calendar.`calendar_eventinvitee` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, + `eventPHID` varbinary(64) NOT NULL, + `inviteePHID` varbinary(64) NOT NULL, + `inviterPHID` varbinary(64) NOT NULL, + `status` VARCHAR(64) COLLATE {$COLLATE_TEXT} NOT NULL, + `dateCreated` int(10) unsigned NOT NULL, + `dateModified` int(10) unsigned NOT NULL, + UNIQUE KEY `key_event` (`eventPHID`, `inviteePHID`), + KEY `key_invitee` (`inviteePHID`) +) ENGINE=InnoDB COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a64cae8f8f..86ab73af8d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1486,6 +1486,8 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', 'PhabricatorCalendarEventInvalidEpochException' => 'applications/calendar/exception/PhabricatorCalendarEventInvalidEpochException.php', + 'PhabricatorCalendarEventInvitee' => 'applications/calendar/storage/PhabricatorCalendarEventInvitee.php', + 'PhabricatorCalendarEventInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php', 'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php', 'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php', 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', @@ -4817,6 +4819,11 @@ phutil_register_library_map(array( 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCalendarEventInvalidEpochException' => 'Exception', + 'PhabricatorCalendarEventInvitee' => array( + 'PhabricatorCalendarDAO', + 'PhabricatorPolicyInterface', + ), + 'PhabricatorCalendarEventInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType', 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 9580296b1f..0b1be3860f 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -16,8 +16,10 @@ final class PhabricatorCalendarEventEditController public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); + $user_phid = $user->getPHID(); $error_name = true; $validation_exception = null; + $invitees = null; $start_time = id(new AphrontFormDateControl()) ->setUser($user) @@ -40,6 +42,9 @@ final class PhabricatorCalendarEventEditController $page_title = pht('Create Event'); $redirect = 'created'; $subscribers = array(); + $invitees = array( + $user_phid => PhabricatorCalendarEventInvitee::STATUS_ATTENDING, + ); } else { $event = id(new PhabricatorCalendarEventQuery()) ->setViewer($user) @@ -112,6 +117,14 @@ final class PhabricatorCalendarEventEditController PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION) ->setNewValue($description); + if ($invitees) { + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_INVITE) + ->setNewValue($invitees); + } + + $editor = id(new PhabricatorCalendarEventEditor()) ->setActor($user) ->setContentSourceFromRequest($request) @@ -160,7 +173,6 @@ final class PhabricatorCalendarEventEditController ->setUser($user) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); - $form = id(new AphrontFormView()) ->setUser($user) ->appendChild($name) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 9214ff2cd0..fa7ef9cc33 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -127,6 +127,20 @@ final class PhabricatorCalendarEventViewController pht('Ends'), phabricator_datetime($event->getDateTo(), $viewer)); + $invitees = $event->getInvitees(); + $invitee_list = new PHUIStatusListView(); + foreach ($invitees as $invitee) { + $item = new PHUIStatusItemView(); + $invitee_phid = $invitee->getInviteePHID(); + $target = $viewer->renderHandle($invitee_phid); + $item->setTarget($target); + $invitee_list->addItem($item); + } + + $properties->addProperty( + pht('Invitees'), + $invitee_list); + $properties->invokeWillRenderEvent(); $properties->addSectionHeader( diff --git a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php index b268738f27..2046e746c7 100644 --- a/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php +++ b/src/applications/calendar/editor/PhabricatorCalendarEventEditor.php @@ -20,6 +20,7 @@ final class PhabricatorCalendarEventEditor $types[] = PhabricatorCalendarEventTransaction::TYPE_STATUS; $types[] = PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION; $types[] = PhabricatorCalendarEventTransaction::TYPE_CANCEL; + $types[] = PhabricatorCalendarEventTransaction::TYPE_INVITE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; @@ -47,6 +48,30 @@ final class PhabricatorCalendarEventEditor return $object->getDescription(); case PhabricatorCalendarEventTransaction::TYPE_CANCEL: return $object->getIsCancelled(); + case PhabricatorCalendarEventTransaction::TYPE_INVITE: + $map = $xaction->getNewValue(); + $phids = array_keys($map); + $invitees = array(); + + if ($map && !$this->getIsNewObject()) { + $invitees = id(new PhabricatorCalendarEventInviteeQuery()) + ->setViewer($this->getActor()) + ->withEventPHIDs(array($object->getPHID())) + ->withInviteePHIDs($phids) + ->execute(); + $invitees = mpull($invitees, null, 'getInviteePHID'); + } + + $old = array(); + foreach ($phids as $phid) { + $invitee = idx($invitees, $phid); + if ($invitee) { + $old[$phid] = $invitee->getStatus(); + } else { + $old[$phid] = PhabricatorCalendarEventInvitee::STATUS_UNINVITED; + } + } + return $old; } return parent::getCustomTransactionOldValue($object, $xaction); @@ -55,13 +80,13 @@ final class PhabricatorCalendarEventEditor protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - switch ($xaction->getTransactionType()) { case PhabricatorCalendarEventTransaction::TYPE_NAME: case PhabricatorCalendarEventTransaction::TYPE_START_DATE: case PhabricatorCalendarEventTransaction::TYPE_END_DATE: case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: + case PhabricatorCalendarEventTransaction::TYPE_INVITE: return $xaction->getNewValue(); case PhabricatorCalendarEventTransaction::TYPE_STATUS: return (int)$xaction->getNewValue(); @@ -93,6 +118,7 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_CANCEL: $object->setIsCancelled((int)$xaction->getNewValue()); return; + case PhabricatorCalendarEventTransaction::TYPE_INVITE: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_EDGE: @@ -114,6 +140,33 @@ final class PhabricatorCalendarEventEditor case PhabricatorCalendarEventTransaction::TYPE_STATUS: case PhabricatorCalendarEventTransaction::TYPE_DESCRIPTION: case PhabricatorCalendarEventTransaction::TYPE_CANCEL: + return; + case PhabricatorCalendarEventTransaction::TYPE_INVITE: + $map = $xaction->getNewValue(); + $phids = array_keys($map); + $invitees = array(); + + if ($map) { + $invitees = id(new PhabricatorCalendarEventInviteeQuery()) + ->setViewer($this->getActor()) + ->withEventPHIDs(array($object->getPHID())) + ->withInviteePHIDs($phids) + ->execute(); + $invitees = mpull($invitees, null, 'getInviteePHID'); + } + + foreach ($phids as $phid) { + $invitee = idx($invitees, $phid); + if (!$invitee) { + $invitee = id(new PhabricatorCalendarEventInvitee()) + ->setEventPHID($object->getPHID()) + ->setInviteePHID($phid) + ->setInviterPHID($this->getActingAsPHID()); + } + $invitee->setStatus($map[$phid]) + ->save(); + } + return; case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_EDGE: diff --git a/src/applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php new file mode 100644 index 0000000000..2cb8d1c983 --- /dev/null +++ b/src/applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php @@ -0,0 +1,99 @@ +ids = $ids; + return $this; + } + + public function withEventPHIDs(array $phids) { + $this->eventPHIDs = $phids; + return $this; + } + + public function withInviteePHIDs(array $phids) { + $this->inviteePHIDs = $phids; + return $this; + } + + public function withInviterPHIDs(array $phids) { + $this->inviterPHIDs = $phids; + return $this; + } + + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + + protected function loadPage() { + $table = new PhabricatorCalendarEventInvitee(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->eventPHIDs) { + $where[] = qsprintf( + $conn_r, + 'eventPHID IN (%Ls)', + $this->eventPHIDs); + } + + if ($this->inviteePHIDs) { + $where[] = qsprintf( + $conn_r, + 'inviteePHID IN (%Ls)', + $this->inviteePHIDs); + } + + if ($this->inviterPHIDs) { + $where[] = qsprintf( + $conn_r, + 'inviterPHID IN (%Ls)', + $this->inviterPHIDs); + } + + if ($this->statuses) { + $where[] = qsprintf( + $conn_r, + 'status = %d', + $this->statuses); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorCalendarApplication'; + } + +} diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 44ebc127aa..5f880eaace 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -121,4 +121,26 @@ final class PhabricatorCalendarEventQuery return 'PhabricatorCalendarApplication'; } + + protected function willFilterPage(array $events) { + $phids = array(); + + foreach ($events as $event) { + $phids[] = $event->getPHID(); + } + + $invitees = id(new PhabricatorCalendarEventInviteeQuery()) + ->setViewer($this->getViewer()) + ->withEventPHIDs($phids) + ->execute(); + $invitees = mgroup($invitees, 'getEventPHID'); + + foreach ($events as $event) { + $event_invitees = idx($invitees, $event->getPHID(), array()); + $event->attachInvitees($event_invitees); + } + + return $events; + } + } diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index b6a31f0325..81bd5fd59d 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -18,6 +18,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO protected $description; protected $isCancelled; + private $invitees = self::ATTACHABLE; + const STATUS_AWAY = 1; const STATUS_SPORADIC = 2; @@ -118,6 +120,15 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return mpull($statuses, null, 'getUserPHID'); } + public function getInvitees() { + return $this->assertAttached($this->invitees); + } + + public function attachInvitees(array $invitees) { + $this->invitees = $invitees; + return $this; + } + /** * Validates data and throws exceptions for non-sensical status * windows diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventInvitee.php b/src/applications/calendar/storage/PhabricatorCalendarEventInvitee.php new file mode 100644 index 0000000000..8b7f9a063a --- /dev/null +++ b/src/applications/calendar/storage/PhabricatorCalendarEventInvitee.php @@ -0,0 +1,64 @@ +setInviterPHID($actor->getPHID()) + ->setStatus(self::STATUS_INVITED) + ->setEventPHID($event->getPHID()); + } + + protected function getConfiguration() { + return array( + self::CONFIG_COLUMN_SCHEMA => array( + 'status' => 'text64', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_event' => array( + 'columns' => array('eventPHID', 'inviteePHID'), + 'unique' => true, + ), + 'key_invitee' => array( + 'columns' => array('inviteePHID'), + ), + ), + ) + parent::getConfiguration(); + } + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } +} diff --git a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php index 4d3b7c5ffa..464f42692d 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEventTransaction.php @@ -9,6 +9,7 @@ final class PhabricatorCalendarEventTransaction const TYPE_STATUS = 'calendar.status'; const TYPE_DESCRIPTION = 'calendar.description'; const TYPE_CANCEL = 'calendar.cancel'; + const TYPE_INVITE = 'calendar.invite'; const MAILTAG_CONTENT = 'calendar-content'; const MAILTAG_OTHER = 'calendar-other'; @@ -35,6 +36,7 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_STATUS: case self::TYPE_DESCRIPTION: case self::TYPE_CANCEL: + case self::TYPE_INVITE: $phids[] = $this->getObjectPHID(); break; } @@ -50,6 +52,7 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_STATUS: case self::TYPE_DESCRIPTION: case self::TYPE_CANCEL: + case self::TYPE_INVITE: return ($old === null); } return parent::shouldHide(); @@ -63,6 +66,7 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_STATUS: case self::TYPE_DESCRIPTION: case self::TYPE_CANCEL: + case self::TYPE_INVITE: return 'fa-pencil'; break; } @@ -131,6 +135,11 @@ final class PhabricatorCalendarEventTransaction $this->renderHandleLink($author_phid)); break; } + case self::TYPE_INVITE: + return pht( + "%s updated the event's invitee list.", + $this->renderHandleLink($author_phid)); + break; } return parent::getTitle(); @@ -216,6 +225,12 @@ final class PhabricatorCalendarEventTransaction $this->renderHandleLink($object_phid)); break; } + case self::TYPE_INVITE: + return pht( + '%s updated the invitee list of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + break; } return parent::getTitleForFeed(); @@ -232,6 +247,7 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_STATUS: case self::TYPE_DESCRIPTION: case self::TYPE_CANCEL: + case self::TYPE_INVITE: return PhabricatorTransactions::COLOR_GREEN; } @@ -284,6 +300,9 @@ final class PhabricatorCalendarEventTransaction case self::TYPE_CANCEL: $tags[] = self::MAILTAG_CONTENT; break; + case self::TYPE_INVITE: + $tags[] = self::MAILTAG_CONTENT; + break; } return $tags; }