mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 11:30:55 +01:00
Support arbitary event invitees when importing events
Summary: Ref T10747. When we import a ".ics" file, represent any attendees as simple external references. For consistency with other areas of the product, I've avoided disclosing email addresses. We'll try to get a real name if we can. (We store addresses and could expose or use them later, or do some kind of masking junk like "epr...ley@g...l.com" which is utterly impossible to figure out.) Test Plan: {F1888367} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10747 Differential Revision: https://secure.phabricator.com/D16759
This commit is contained in:
parent
8e9c20c9ae
commit
09775279a9
7 changed files with 313 additions and 2 deletions
|
@ -0,0 +1,12 @@
|
|||
CREATE TABLE {$NAMESPACE}_calendar.calendar_externalinvitee (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
name LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
nameIndex BINARY(12) NOT NULL,
|
||||
uri LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
parameters LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
sourcePHID VARBINARY(64) NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_name` (`nameIndex`)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -2098,6 +2098,9 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCalendarExportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php',
|
||||
'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php',
|
||||
'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.php',
|
||||
'PhabricatorCalendarExternalInvitee' => 'applications/calendar/storage/PhabricatorCalendarExternalInvitee.php',
|
||||
'PhabricatorCalendarExternalInviteePHIDType' => 'applications/calendar/phid/PhabricatorCalendarExternalInviteePHIDType.php',
|
||||
'PhabricatorCalendarExternalInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarExternalInviteeQuery.php',
|
||||
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
|
||||
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
|
||||
'PhabricatorCalendarICSFileImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php',
|
||||
|
@ -6939,6 +6942,12 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType',
|
||||
'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController',
|
||||
'PhabricatorCalendarExternalInvitee' => array(
|
||||
'PhabricatorCalendarDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PhabricatorCalendarExternalInviteePHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorCalendarExternalInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
|
||||
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorCalendarICSFileImportEngine' => 'PhabricatorCalendarICSImportEngine',
|
||||
|
|
|
@ -207,6 +207,8 @@ abstract class PhabricatorCalendarImportEngine
|
|||
|
||||
$xactions = array();
|
||||
$update_map = array();
|
||||
$invitee_map = array();
|
||||
$attendee_map = array();
|
||||
foreach ($node_map as $full_uid => $node) {
|
||||
$event = idx($events, $full_uid);
|
||||
if (!$event) {
|
||||
|
@ -222,6 +224,66 @@ abstract class PhabricatorCalendarImportEngine
|
|||
$this->updateEventFromNode($viewer, $event, $node);
|
||||
$xactions[$full_uid] = $this->newUpdateTransactions($event, $node);
|
||||
$update_map[$full_uid] = $event;
|
||||
|
||||
$attendees = $node->getAttendees();
|
||||
$private_index = 1;
|
||||
foreach ($attendees as $attendee) {
|
||||
// Generate a "name" for this attendee which is not an email address.
|
||||
// We avoid disclosing email addresses to be consistent with the rest
|
||||
// of the product.
|
||||
$name = $attendee->getName();
|
||||
if (preg_match('/@/', $name)) {
|
||||
$name = new PhutilEmailAddress($name);
|
||||
$name = $name->getDisplayName();
|
||||
}
|
||||
|
||||
// If we don't have a name or the name still looks like it's an
|
||||
// email address, give them a dummy placeholder name.
|
||||
if (!strlen($name) || preg_match('/@/', $name)) {
|
||||
$name = pht('Private User %d', $private_index);
|
||||
$private_index++;
|
||||
}
|
||||
|
||||
$attendee_map[$full_uid][$name] = $attendee;
|
||||
}
|
||||
}
|
||||
|
||||
$attendee_names = array();
|
||||
foreach ($attendee_map as $full_uid => $event_attendees) {
|
||||
foreach ($event_attendees as $name => $attendee) {
|
||||
$attendee_names[$name] = $attendee;
|
||||
}
|
||||
}
|
||||
|
||||
if ($attendee_names) {
|
||||
$external_invitees = id(new PhabricatorCalendarExternalInviteeQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames($attendee_names)
|
||||
->execute();
|
||||
$external_invitees = mpull($external_invitees, null, 'getName');
|
||||
|
||||
foreach ($attendee_names as $name => $attendee) {
|
||||
if (isset($external_invitees[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$external_invitee = id(new PhabricatorCalendarExternalInvitee())
|
||||
->setName($name)
|
||||
->setURI($attendee->getURI())
|
||||
->setSourcePHID($import->getPHID());
|
||||
|
||||
try {
|
||||
$external_invitee->save();
|
||||
} catch (AphrontDuplicateKeyQueryException $ex) {
|
||||
$external_invitee =
|
||||
id(new PhabricatorCalendarExternalInviteeQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames(array($name))
|
||||
->executeOne();
|
||||
}
|
||||
|
||||
$external_invitees[$name] = $external_invitee;
|
||||
}
|
||||
}
|
||||
|
||||
// Reorder events so we create parents first. This allows us to populate
|
||||
|
@ -288,6 +350,51 @@ abstract class PhabricatorCalendarImportEngine
|
|||
|
||||
$editor->applyTransactions($event, $event_xactions);
|
||||
|
||||
// We're just forcing attendees to the correct values here because
|
||||
// transactions intentionally don't let you RSVP for other users. This
|
||||
// might need to be turned into a special type of transaction eventually.
|
||||
$attendees = $attendee_map[$full_uid];
|
||||
$old_map = $event->getInvitees();
|
||||
$old_map = mpull($old_map, null, 'getInviteePHID');
|
||||
|
||||
$new_map = array();
|
||||
foreach ($attendees as $name => $attendee) {
|
||||
$phid = $external_invitees[$name]->getPHID();
|
||||
|
||||
$invitee = idx($old_map, $phid);
|
||||
if (!$invitee) {
|
||||
$invitee = id(new PhabricatorCalendarEventInvitee())
|
||||
->setEventPHID($event->getPHID())
|
||||
->setInviteePHID($phid)
|
||||
->setInviterPHID($import->getPHID());
|
||||
}
|
||||
|
||||
switch ($attendee->getStatus()) {
|
||||
case PhutilCalendarUserNode::STATUS_ACCEPTED:
|
||||
$status = PhabricatorCalendarEventInvitee::STATUS_ATTENDING;
|
||||
break;
|
||||
case PhutilCalendarUserNode::STATUS_DECLINED:
|
||||
$status = PhabricatorCalendarEventInvitee::STATUS_DECLINED;
|
||||
break;
|
||||
case PhutilCalendarUserNode::STATUS_INVITED:
|
||||
default:
|
||||
$status = PhabricatorCalendarEventInvitee::STATUS_INVITED;
|
||||
break;
|
||||
}
|
||||
$invitee->setStatus($status);
|
||||
$invitee->save();
|
||||
|
||||
$new_map[$phid] = $invitee;
|
||||
}
|
||||
|
||||
foreach ($old_map as $phid => $invitee) {
|
||||
if (empty($new_map[$phid])) {
|
||||
$invitee->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$event->attachInvitees($new_map);
|
||||
|
||||
$import->newLogMessage(
|
||||
PhabricatorCalendarImportUpdateLogType::LOGTYPE,
|
||||
array(
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExternalInviteePHIDType
|
||||
extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'CXNV';
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('External Invitee');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new PhabricatorCalendarExternalInvitee();
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorCalendarApplication';
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new PhabricatorCalendarExternalInviteeQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
foreach ($handles as $phid => $handle) {
|
||||
$invitee = $objects[$phid];
|
||||
|
||||
$name = $invitee->getName();
|
||||
$handle->setName($name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExternalInviteeQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $names;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withNames(array $names) {
|
||||
$this->names = $names;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new PhabricatorCalendarExternalInvitee();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->names !== null) {
|
||||
$name_indexes = array();
|
||||
foreach ($this->names as $name) {
|
||||
$name_indexes[] = PhabricatorHash::digestForIndex($name);
|
||||
}
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'nameIndex IN (%Ls)',
|
||||
$name_indexes);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorCalendarApplication';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCalendarExternalInvitee
|
||||
extends PhabricatorCalendarDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $name;
|
||||
protected $nameIndex;
|
||||
protected $uri;
|
||||
protected $parameters = array();
|
||||
protected $sourcePHID;
|
||||
|
||||
public static function initializeNewCalendarEventInvitee(
|
||||
PhabricatorUser $actor, $event) {
|
||||
return id(new PhabricatorCalendarEventInvitee())
|
||||
->setInviterPHID($actor->getPHID())
|
||||
->setStatus(self::STATUS_INVITED)
|
||||
->setEventPHID($event->getPHID());
|
||||
}
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'parameters' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'name' => 'text',
|
||||
'nameIndex' => 'bytes12',
|
||||
'uri' => 'text',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_name' => array(
|
||||
'columns' => array('nameIndex'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function getPHIDType() {
|
||||
return PhabricatorCalendarExternalInviteePHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
|
||||
return parent::save();
|
||||
}
|
||||
|
||||
|
||||
/* -( 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;
|
||||
}
|
||||
}
|
|
@ -30,10 +30,11 @@ final class PhabricatorCalendarEventInviteTransaction
|
|||
|
||||
$map = array();
|
||||
foreach ($add as $phid) {
|
||||
$map[$phid] = $status_invited;
|
||||
$map[$phid] = $status_invited;
|
||||
}
|
||||
|
||||
foreach ($rem as $phid) {
|
||||
$map[$phid] = $status_uninvited;
|
||||
$map[$phid] = $status_uninvited;
|
||||
}
|
||||
|
||||
// If we're creating this event and the actor is inviting themselves,
|
||||
|
|
Loading…
Reference in a new issue