1
0
Fork 0
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:
epriestley 2016-10-27 10:58:11 -07:00
parent 8e9c20c9ae
commit 09775279a9
7 changed files with 313 additions and 2 deletions

View file

@ -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};

View file

@ -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',

View file

@ -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(

View file

@ -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);
}
}
}

View file

@ -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';
}
}

View file

@ -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;
}
}

View file

@ -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,