mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-17 20:32:41 +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',
|
'PhabricatorCalendarExportTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarExportTransactionQuery.php',
|
||||||
'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php',
|
'PhabricatorCalendarExportTransactionType' => 'applications/calendar/xaction/PhabricatorCalendarExportTransactionType.php',
|
||||||
'PhabricatorCalendarExportViewController' => 'applications/calendar/controller/PhabricatorCalendarExportViewController.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',
|
'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php',
|
||||||
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
|
'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php',
|
||||||
'PhabricatorCalendarICSFileImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php',
|
'PhabricatorCalendarICSFileImportEngine' => 'applications/calendar/import/PhabricatorCalendarICSFileImportEngine.php',
|
||||||
|
@ -6939,6 +6942,12 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PhabricatorCalendarExportTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType',
|
'PhabricatorCalendarExportTransactionType' => 'PhabricatorModularTransactionType',
|
||||||
'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController',
|
'PhabricatorCalendarExportViewController' => 'PhabricatorCalendarController',
|
||||||
|
'PhabricatorCalendarExternalInvitee' => array(
|
||||||
|
'PhabricatorCalendarDAO',
|
||||||
|
'PhabricatorPolicyInterface',
|
||||||
|
),
|
||||||
|
'PhabricatorCalendarExternalInviteePHIDType' => 'PhabricatorPHIDType',
|
||||||
|
'PhabricatorCalendarExternalInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
|
'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO',
|
||||||
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
|
'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorCalendarICSFileImportEngine' => 'PhabricatorCalendarICSImportEngine',
|
'PhabricatorCalendarICSFileImportEngine' => 'PhabricatorCalendarICSImportEngine',
|
||||||
|
|
|
@ -207,6 +207,8 @@ abstract class PhabricatorCalendarImportEngine
|
||||||
|
|
||||||
$xactions = array();
|
$xactions = array();
|
||||||
$update_map = array();
|
$update_map = array();
|
||||||
|
$invitee_map = array();
|
||||||
|
$attendee_map = array();
|
||||||
foreach ($node_map as $full_uid => $node) {
|
foreach ($node_map as $full_uid => $node) {
|
||||||
$event = idx($events, $full_uid);
|
$event = idx($events, $full_uid);
|
||||||
if (!$event) {
|
if (!$event) {
|
||||||
|
@ -222,6 +224,66 @@ abstract class PhabricatorCalendarImportEngine
|
||||||
$this->updateEventFromNode($viewer, $event, $node);
|
$this->updateEventFromNode($viewer, $event, $node);
|
||||||
$xactions[$full_uid] = $this->newUpdateTransactions($event, $node);
|
$xactions[$full_uid] = $this->newUpdateTransactions($event, $node);
|
||||||
$update_map[$full_uid] = $event;
|
$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
|
// Reorder events so we create parents first. This allows us to populate
|
||||||
|
@ -288,6 +350,51 @@ abstract class PhabricatorCalendarImportEngine
|
||||||
|
|
||||||
$editor->applyTransactions($event, $event_xactions);
|
$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(
|
$import->newLogMessage(
|
||||||
PhabricatorCalendarImportUpdateLogType::LOGTYPE,
|
PhabricatorCalendarImportUpdateLogType::LOGTYPE,
|
||||||
array(
|
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();
|
$map = array();
|
||||||
foreach ($add as $phid) {
|
foreach ($add as $phid) {
|
||||||
$map[$phid] = $status_invited;
|
$map[$phid] = $status_invited;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($rem as $phid) {
|
foreach ($rem as $phid) {
|
||||||
$map[$phid] = $status_uninvited;
|
$map[$phid] = $status_uninvited;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're creating this event and the actor is inviting themselves,
|
// If we're creating this event and the actor is inviting themselves,
|
||||||
|
|
Loading…
Reference in a new issue