Page MenuHomestyx hydra

No OneTemporary

diff --git a/resources/sql/autopatches/20170413.conpherence.01.recentparty.sql b/resources/sql/autopatches/20170413.conpherence.01.recentparty.sql
new file mode 100644
index 0000000000..996a058c5b
--- /dev/null
+++ b/resources/sql/autopatches/20170413.conpherence.01.recentparty.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread
+ DROP COLUMN recentParticipantPHIDs;
diff --git a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php
index 1fb4dbabaa..85c4b9c1f1 100644
--- a/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php
+++ b/src/applications/conpherence/__tests__/ConpherenceRoomTestCase.php
@@ -1,157 +1,144 @@
<?php
final class ConpherenceRoomTestCase extends ConpherenceTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testOneUserRoomCreate() {
$creator = $this->generateNewTestUser();
$participant_phids = array($creator->getPHID());
$conpherence = $this->createRoom($creator, $participant_phids);
$this->assertTrue((bool)$conpherence->getID());
$this->assertEqual(1, count($conpherence->getParticipants()));
- $this->assertEqual(
- $participant_phids,
- $conpherence->getRecentParticipantPHIDs());
}
public function testNUserRoomCreate() {
$creator = $this->generateNewTestUser();
$friend_1 = $this->generateNewTestUser();
$friend_2 = $this->generateNewTestUser();
$friend_3 = $this->generateNewTestUser();
$participant_phids = array(
$creator->getPHID(),
$friend_1->getPHID(),
$friend_2->getPHID(),
$friend_3->getPHID(),
);
$conpherence = $this->createRoom($creator, $participant_phids);
$this->assertTrue((bool)$conpherence->getID());
$this->assertEqual(4, count($conpherence->getParticipants()));
- $this->assertEqual(
- $participant_phids,
- $conpherence->getRecentParticipantPHIDs());
}
public function testRoomParticipantAddition() {
$creator = $this->generateNewTestUser();
$friend_1 = $this->generateNewTestUser();
$friend_2 = $this->generateNewTestUser();
$friend_3 = $this->generateNewTestUser();
$participant_phids = array(
$creator->getPHID(),
$friend_1->getPHID(),
);
$conpherence = $this->createRoom($creator, $participant_phids);
$this->assertTrue((bool)$conpherence->getID());
$this->assertEqual(2, count($conpherence->getParticipants()));
- $this->assertEqual(
- $participant_phids,
- $conpherence->getRecentParticipantPHIDs());
// test add by creator
$participant_phids[] = $friend_2->getPHID();
$this->addParticipants($creator, $conpherence, array($friend_2->getPHID()));
- $this->assertEqual(
- $participant_phids,
- $conpherence->getRecentParticipantPHIDs());
+ $this->assertEqual(3, count($conpherence->getParticipants()));
// test add by other participant, so recent participation should
// meaningfully change
$participant_phids = array(
$friend_2->getPHID(), // actor
$creator->getPHID(), // last actor
$friend_1->getPHID(),
$friend_3->getPHID(), // new addition
);
$this->addParticipants(
$friend_2,
$conpherence,
array($friend_3->getPHID()));
- $this->assertEqual(
- $participant_phids,
- $conpherence->getRecentParticipantPHIDs());
+ $this->assertEqual(4, count($conpherence->getParticipants()));
}
public function testRoomParticipantDeletion() {
$creator = $this->generateNewTestUser();
$friend_1 = $this->generateNewTestUser();
$friend_2 = $this->generateNewTestUser();
$friend_3 = $this->generateNewTestUser();
$participant_map = array(
$creator->getPHID() => $creator,
$friend_1->getPHID() => $friend_1,
$friend_2->getPHID() => $friend_2,
$friend_3->getPHID() => $friend_3,
);
$conpherence = $this->createRoom(
$creator,
array_keys($participant_map));
foreach ($participant_map as $phid => $user) {
$this->removeParticipants($user, $conpherence, array($phid));
unset($participant_map[$phid]);
$this->assertEqual(
count($participant_map),
count($conpherence->getParticipants()));
}
}
private function createRoom(
PhabricatorUser $creator,
array $participant_phids) {
$conpherence = ConpherenceThread::initializeNewRoom($creator);
$xactions = array();
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS)
->setNewValue(array('+' => $participant_phids));
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(
ConpherenceThreadTitleTransaction::TRANSACTIONTYPE)
->setNewValue(pht('Test'));
id(new ConpherenceEditor())
->setActor($creator)
->setContentSource($this->newContentSource())
->setContinueOnNoEffect(true)
->applyTransactions($conpherence, $xactions);
return $conpherence;
}
private function changeEditPolicy(
PhabricatorUser $actor,
ConpherenceThread $room,
$policy) {
$xactions = array();
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($policy);
id(new ConpherenceEditor())
->setActor($actor)
->setContentSource($this->newContentSource())
->setContinueOnNoEffect(true)
->applyTransactions($room, $xactions);
}
}
diff --git a/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php b/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php
index c66a90f585..fd602058a3 100644
--- a/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php
+++ b/src/applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php
@@ -1,81 +1,80 @@
<?php
final class ConpherenceQueryThreadConduitAPIMethod
extends ConpherenceConduitAPIMethod {
public function getAPIMethodName() {
return 'conpherence.querythread';
}
public function getMethodDescription() {
return pht(
'Query for Conpherence threads for the logged in user. You can query '.
'by IDs or PHIDs for specific Conpherence threads. Otherwise, specify '.
'limit and offset to query the most recently updated Conpherences for '.
'the logged in user.');
}
protected function defineParamTypes() {
return array(
'ids' => 'optional array<int>',
'phids' => 'optional array<phids>',
'limit' => 'optional int',
'offset' => 'optional int',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function execute(ConduitAPIRequest $request) {
$user = $request->getUser();
$ids = $request->getValue('ids', array());
$phids = $request->getValue('phids', array());
$limit = $request->getValue('limit');
$offset = $request->getValue('offset');
$query = id(new ConpherenceThreadQuery())
->setViewer($user)
->needParticipantCache(true);
if ($ids) {
$conpherences = $query
->withIDs($ids)
->setLimit($limit)
->setOffset($offset)
->execute();
} else if ($phids) {
$conpherences = $query
->withPHIDs($phids)
->setLimit($limit)
->setOffset($offset)
->execute();
} else {
$participation = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($user->getPHID()))
->setLimit($limit)
->setOffset($offset)
->execute();
$conpherence_phids = array_keys($participation);
$query->withPHIDs($conpherence_phids);
$conpherences = $query->execute();
$conpherences = array_select_keys($conpherences, $conpherence_phids);
}
$data = array();
foreach ($conpherences as $conpherence) {
$id = $conpherence->getID();
$data[$id] = array(
'conpherenceID' => $id,
'conpherencePHID' => $conpherence->getPHID(),
'conpherenceTitle' => $conpherence->getTitle(),
'messageCount' => $conpherence->getMessageCount(),
- 'recentParticipantPHIDs' => $conpherence->getRecentParticipantPHIDs(),
'conpherenceURI' => $this->getConpherenceURI($conpherence),
);
}
return $data;
}
}
diff --git a/src/applications/conpherence/controller/ConpherenceColumnViewController.php b/src/applications/conpherence/controller/ConpherenceColumnViewController.php
index de83379aab..aa4f94edfc 100644
--- a/src/applications/conpherence/controller/ConpherenceColumnViewController.php
+++ b/src/applications/conpherence/controller/ConpherenceColumnViewController.php
@@ -1,109 +1,108 @@
<?php
final class ConpherenceColumnViewController extends
ConpherenceController {
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
$latest_conpherences = array();
$latest_participant = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($user->getPHID()))
->setLimit(8)
->execute();
if ($latest_participant) {
$conpherence_phids = mpull($latest_participant, 'getConpherencePHID');
$latest_conpherences = id(new ConpherenceThreadQuery())
->setViewer($user)
->withPHIDs($conpherence_phids)
->needProfileImage(true)
- ->needParticipantCache(true)
->execute();
$latest_conpherences = mpull($latest_conpherences, null, 'getPHID');
$latest_conpherences = array_select_keys(
$latest_conpherences,
$conpherence_phids);
}
$conpherence = null;
$should_404 = false;
if ($request->getInt('id')) {
$conpherence = id(new ConpherenceThreadQuery())
->setViewer($user)
->withIDs(array($request->getInt('id')))
->needProfileImage(true)
->needTransactions(true)
->setTransactionLimit(ConpherenceThreadQuery::TRANSACTION_LIMIT)
->executeOne();
$should_404 = true;
} else if ($latest_participant) {
$participant = head($latest_participant);
$conpherence = id(new ConpherenceThreadQuery())
->setViewer($user)
->withPHIDs(array($participant->getConpherencePHID()))
->needProfileImage(true)
->needTransactions(true)
->setTransactionLimit(ConpherenceThreadQuery::TRANSACTION_LIMIT)
->executeOne();
$should_404 = true;
}
$durable_column = id(new ConpherenceDurableColumnView())
->setUser($user)
->setVisible(true);
if (!$conpherence) {
if ($should_404) {
return new Aphront404Response();
}
$conpherence_id = null;
$conpherence_phid = null;
$latest_transaction_id = null;
$can_edit = false;
} else {
$this->setConpherence($conpherence);
$participant = $conpherence->getParticipant($user->getPHID());
$transactions = $conpherence->getTransactions();
$latest_transaction = head($transactions);
$write_guard = AphrontWriteGuard::beginScopedUnguardedWrites();
$participant->markUpToDate($conpherence, $latest_transaction);
unset($write_guard);
$draft = PhabricatorDraft::newFromUserAndKey(
$user,
$conpherence->getPHID());
$durable_column
->setDraft($draft)
->setSelectedConpherence($conpherence)
->setConpherences($latest_conpherences);
$conpherence_id = $conpherence->getID();
$conpherence_phid = $conpherence->getPHID();
$latest_transaction_id = $latest_transaction->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$conpherence,
PhabricatorPolicyCapability::CAN_EDIT);
}
$dropdown_query = id(new AphlictDropdownDataQuery())
->setViewer($user);
$dropdown_query->execute();
$response = array(
'content' => hsprintf('%s', $durable_column),
'threadID' => $conpherence_id,
'threadPHID' => $conpherence_phid,
'latestTransactionID' => $latest_transaction_id,
'canEdit' => $can_edit,
'aphlictDropdownData' => array(
$dropdown_query->getNotificationData(),
$dropdown_query->getConpherenceData(),
),
);
return id(new AphrontAjaxResponse())->setContent($response);
}
}
diff --git a/src/applications/conpherence/controller/ConpherenceListController.php b/src/applications/conpherence/controller/ConpherenceListController.php
index 4e81526004..00968af576 100644
--- a/src/applications/conpherence/controller/ConpherenceListController.php
+++ b/src/applications/conpherence/controller/ConpherenceListController.php
@@ -1,172 +1,171 @@
<?php
final class ConpherenceListController extends ConpherenceController {
const SELECTED_MODE = 'selected';
const UNSELECTED_MODE = 'unselected';
/**
* Two main modes of operation...
*
* 1 - /conpherence/ - UNSELECTED_MODE
* 2 - /conpherence/<id>/ - SELECTED_MODE
*
* UNSELECTED_MODE is not an Ajax request while the other two are Ajax
* requests.
*/
private function determineMode() {
$request = $this->getRequest();
$mode = self::UNSELECTED_MODE;
if ($request->isAjax()) {
$mode = self::SELECTED_MODE;
}
return $mode;
}
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
$title = pht('Conpherence');
$conpherence = null;
$limit = (ConpherenceThreadListView::SEE_MORE_LIMIT * 2) + 1;
$all_participation = array();
$mode = $this->determineMode();
switch ($mode) {
case self::SELECTED_MODE:
$conpherence_id = $request->getURIData('id');
$conpherence = id(new ConpherenceThreadQuery())
->setViewer($user)
->withIDs(array($conpherence_id))
->executeOne();
if (!$conpherence) {
return new Aphront404Response();
}
if ($conpherence->getTitle()) {
$title = $conpherence->getTitle();
}
$cursor = $conpherence->getParticipantIfExists($user->getPHID());
$data = $this->loadDefaultParticipation($limit);
$all_participation = $data['all_participation'];
if (!$cursor) {
$menu_participation = id(new ConpherenceParticipant())
->makeEphemeral()
->setConpherencePHID($conpherence->getPHID())
->setParticipantPHID($user->getPHID());
} else {
$menu_participation = $cursor;
}
// check to see if the loaded conpherence is going to show up
// within the SEE_MORE_LIMIT amount of conpherences.
// If its not there, then we just pre-pend it as the "first"
// conpherence so folks have a navigation item in the menu.
$count = 0;
$found = false;
foreach ($all_participation as $phid => $curr_participation) {
if ($conpherence->getPHID() == $phid) {
$found = true;
break;
}
$count++;
if ($count > ConpherenceThreadListView::SEE_MORE_LIMIT) {
break;
}
}
if (!$found) {
$all_participation =
array($conpherence->getPHID() => $menu_participation) +
$all_participation;
}
break;
case self::UNSELECTED_MODE:
default:
$data = $this->loadDefaultParticipation($limit);
$all_participation = $data['all_participation'];
break;
}
$threads = $this->loadConpherenceThreadData(
$all_participation);
$thread_view = id(new ConpherenceThreadListView())
->setUser($user)
->setBaseURI($this->getApplicationURI())
->setThreads($threads);
switch ($mode) {
case self::SELECTED_MODE:
$response = id(new AphrontAjaxResponse())->setContent($thread_view);
break;
case self::UNSELECTED_MODE:
default:
$layout = id(new ConpherenceLayoutView())
->setUser($user)
->setBaseURI($this->getApplicationURI())
->setThreadView($thread_view)
->setRole('list');
if ($conpherence) {
$layout->setThread($conpherence);
} else {
// make a dummy conpherence so we can render something
$conpherence = ConpherenceThread::initializeNewRoom($user);
$conpherence->attachHandles(array());
$conpherence->attachTransactions(array());
$conpherence->makeEphemeral();
}
$policy_objects = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($conpherence)
->execute();
$layout->setHeader($this->buildHeaderPaneContent(
$conpherence,
$policy_objects));
$response = $this->newPage()
->setTitle($title)
->appendChild($layout);
break;
}
return $response;
}
private function loadDefaultParticipation($limit) {
$viewer = $this->getRequest()->getUser();
$all_participation = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($viewer->getPHID()))
->setLimit($limit)
->execute();
return array(
'all_participation' => $all_participation,
);
}
private function loadConpherenceThreadData($participation) {
$user = $this->getRequest()->getUser();
$conpherence_phids = array_keys($participation);
$conpherences = array();
if ($conpherence_phids) {
$conpherences = id(new ConpherenceThreadQuery())
->setViewer($user)
->withPHIDs($conpherence_phids)
->needProfileImage(true)
- ->needParticipantCache(true)
->execute();
// this will re-sort by participation data
$conpherences = array_select_keys($conpherences, $conpherence_phids);
}
return $conpherences;
}
}
diff --git a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php
index 402da2aac3..ed728c6ab7 100644
--- a/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php
+++ b/src/applications/conpherence/controller/ConpherenceNotificationPanelController.php
@@ -1,139 +1,138 @@
<?php
final class ConpherenceNotificationPanelController
extends ConpherenceController {
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
$conpherences = array();
require_celerity_resource('conpherence-notification-css');
$unread_status = ConpherenceParticipationStatus::BEHIND;
$participant_data = id(new ConpherenceParticipantQuery())
->withParticipantPHIDs(array($user->getPHID()))
->setLimit(5)
->execute();
if ($participant_data) {
$conpherences = id(new ConpherenceThreadQuery())
->setViewer($user)
->withPHIDs(array_keys($participant_data))
->needProfileImage(true)
->needTransactions(true)
->setTransactionLimit(100)
- ->needParticipantCache(true)
->execute();
}
if ($conpherences) {
// re-order the conpherences based on participation data
$conpherences = array_select_keys(
$conpherences, array_keys($participant_data));
$view = new AphrontNullView();
foreach ($conpherences as $conpherence) {
$p_data = $participant_data[$conpherence->getPHID()];
$d_data = $conpherence->getDisplayData($user);
$classes = array(
'phabricator-notification',
'conpherence-notification',
);
if ($p_data->getParticipationStatus() == $unread_status) {
$classes[] = 'phabricator-notification-unread';
}
$uri = $this->getApplicationURI($conpherence->getID().'/');
$title = $d_data['title'];
$subtitle = $d_data['subtitle'];
$unread_count = $d_data['unread_count'];
$epoch = $d_data['epoch'];
$image = $d_data['image'];
$msg_view = id(new ConpherenceMenuItemView())
->setUser($user)
->setTitle($title)
->setSubtitle($subtitle)
->setHref($uri)
->setEpoch($epoch)
->setImageURI($image)
->setUnreadCount($unread_count);
$view->appendChild(javelin_tag(
'div',
array(
'class' => implode(' ', $classes),
'sigil' => 'notification',
'meta' => array(
'href' => $uri,
),
),
$msg_view));
}
$content = $view->render();
} else {
$rooms_uri = phutil_tag(
'a',
array(
'href' => '/conpherence/',
'class' => 'no-room-notification',
),
pht('You have joined no rooms.'));
$content = phutil_tag_div(
'phabricator-notification no-notifications', $rooms_uri);
}
$content = hsprintf(
'<div class="phabricator-notification-header grouped">%s%s</div>'.
'%s',
phutil_tag(
'a',
array(
'href' => '/conpherence/',
),
pht('Rooms')),
$this->renderPersistentOption(),
$content);
$unread = id(new ConpherenceParticipantCountQuery())
->withParticipantPHIDs(array($user->getPHID()))
->withParticipationStatus($unread_status)
->execute();
$unread_count = idx($unread, $user->getPHID(), 0);
$json = array(
'content' => $content,
'number' => (int)$unread_count,
);
return id(new AphrontAjaxResponse())->setContent($json);
}
private function renderPersistentOption() {
$viewer = $this->getViewer();
$column_key = PhabricatorConpherenceColumnVisibleSetting::SETTINGKEY;
$show = (bool)$viewer->getUserSetting($column_key, false);
$view = phutil_tag(
'div',
array(
'class' => 'persistent-option',
),
array(
javelin_tag(
'input',
array(
'type' => 'checkbox',
'checked' => ($show) ? 'checked' : null,
'value' => !$show,
'sigil' => 'conpherence-persist-column',
)),
phutil_tag(
'span',
array(),
pht('Persistent Chat')),
));
return $view;
}
}
diff --git a/src/applications/conpherence/controller/ConpherenceUpdateController.php b/src/applications/conpherence/controller/ConpherenceUpdateController.php
index b8ebe5a826..9cc666109c 100644
--- a/src/applications/conpherence/controller/ConpherenceUpdateController.php
+++ b/src/applications/conpherence/controller/ConpherenceUpdateController.php
@@ -1,575 +1,573 @@
<?php
final class ConpherenceUpdateController
extends ConpherenceController {
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
$conpherence_id = $request->getURIData('id');
if (!$conpherence_id) {
return new Aphront404Response();
}
$need_participants = false;
$needed_capabilities = array(PhabricatorPolicyCapability::CAN_VIEW);
$action = $request->getStr('action', ConpherenceUpdateActions::METADATA);
switch ($action) {
case ConpherenceUpdateActions::REMOVE_PERSON:
$person_phid = $request->getStr('remove_person');
if ($person_phid != $user->getPHID()) {
$needed_capabilities[] = PhabricatorPolicyCapability::CAN_EDIT;
}
break;
case ConpherenceUpdateActions::ADD_PERSON:
case ConpherenceUpdateActions::METADATA:
$needed_capabilities[] = PhabricatorPolicyCapability::CAN_EDIT;
break;
case ConpherenceUpdateActions::NOTIFICATIONS:
$need_participants = true;
break;
case ConpherenceUpdateActions::LOAD:
break;
}
$conpherence = id(new ConpherenceThreadQuery())
->setViewer($user)
->withIDs(array($conpherence_id))
->needParticipants($need_participants)
->requireCapabilities($needed_capabilities)
->executeOne();
$latest_transaction_id = null;
$response_mode = $request->isAjax() ? 'ajax' : 'redirect';
$error_view = null;
$e_file = array();
$errors = array();
$delete_draft = false;
$xactions = array();
if ($request->isFormPost() || ($action == ConpherenceUpdateActions::LOAD)) {
$editor = id(new ConpherenceEditor())
->setContinueOnNoEffect($request->isContinueRequest())
->setContentSourceFromRequest($request)
->setActor($user);
switch ($action) {
case ConpherenceUpdateActions::DRAFT:
$draft = PhabricatorDraft::newFromUserAndKey(
$user,
$conpherence->getPHID());
$draft->setDraft($request->getStr('text'));
$draft->replaceOrDelete();
return new AphrontAjaxResponse();
case ConpherenceUpdateActions::JOIN_ROOM:
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(
ConpherenceTransaction::TYPE_PARTICIPANTS)
->setNewValue(array('+' => array($user->getPHID())));
$delete_draft = true;
$message = $request->getStr('text');
if ($message) {
$message_xactions = $editor->generateTransactionsFromText(
$user,
$conpherence,
$message);
$xactions = array_merge($xactions, $message_xactions);
}
// for now, just redirect back to the conpherence so everything
// will work okay...!
$response_mode = 'redirect';
break;
case ConpherenceUpdateActions::MESSAGE:
$message = $request->getStr('text');
if (strlen($message)) {
$xactions = $editor->generateTransactionsFromText(
$user,
$conpherence,
$message);
$delete_draft = true;
} else {
$action = ConpherenceUpdateActions::LOAD;
$updated = false;
$response_mode = 'ajax';
}
break;
case ConpherenceUpdateActions::ADD_PERSON:
$person_phids = $request->getArr('add_person');
if (!empty($person_phids)) {
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(
ConpherenceTransaction::TYPE_PARTICIPANTS)
->setNewValue(array('+' => $person_phids));
}
break;
case ConpherenceUpdateActions::REMOVE_PERSON:
if (!$request->isContinueRequest()) {
// do nothing; we'll display a confirmation dialog instead
break;
}
$person_phid = $request->getStr('remove_person');
if ($person_phid) {
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(
ConpherenceTransaction::TYPE_PARTICIPANTS)
->setNewValue(array('-' => array($person_phid)));
$response_mode = 'go-home';
}
break;
case ConpherenceUpdateActions::NOTIFICATIONS:
$notifications = $request->getStr('notifications');
$participant = $conpherence->getParticipantIfExists($user->getPHID());
if (!$participant) {
return id(new Aphront404Response());
}
$participant->setSettings(array('notifications' => $notifications));
$participant->save();
return id(new AphrontRedirectResponse())
->setURI('/'.$conpherence->getMonogram());
break;
case ConpherenceUpdateActions::METADATA:
$title = $request->getStr('title');
$topic = $request->getStr('topic');
// all other metadata updates are continue requests
if (!$request->isContinueRequest()) {
break;
}
$title = $request->getStr('title');
$topic = $request->getStr('topic');
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(
ConpherenceThreadTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title);
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(
ConpherenceThreadTopicTransaction::TRANSACTIONTYPE)
->setNewValue($topic);
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($request->getStr('viewPolicy'));
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($request->getStr('editPolicy'));
if (!$request->getExists('force_ajax')) {
$response_mode = 'redirect';
}
break;
case ConpherenceUpdateActions::LOAD:
$updated = false;
$response_mode = 'ajax';
break;
default:
throw new Exception(pht('Unknown action: %s', $action));
break;
}
if ($xactions) {
try {
$xactions = $editor->applyTransactions($conpherence, $xactions);
if ($delete_draft) {
$draft = PhabricatorDraft::newFromUserAndKey(
$user,
$conpherence->getPHID());
$draft->delete();
}
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($this->getApplicationURI($conpherence_id.'/'))
->setException($ex);
}
// xactions had no effect...!
if (empty($xactions)) {
$errors[] = pht(
'That was a non-update. Try cancel.');
}
}
if ($xactions || ($action == ConpherenceUpdateActions::LOAD)) {
switch ($response_mode) {
case 'ajax':
$latest_transaction_id = $request->getInt('latest_transaction_id');
$content = $this->loadAndRenderUpdates(
$action,
$conpherence_id,
$latest_transaction_id);
return id(new AphrontAjaxResponse())
->setContent($content);
break;
case 'go-home':
$content = array(
'href' => $this->getApplicationURI(),
);
return id(new AphrontAjaxResponse())
->setContent($content);
break;
case 'redirect':
default:
return id(new AphrontRedirectResponse())
->setURI('/'.$conpherence->getMonogram());
break;
}
}
}
if ($errors) {
$error_view = id(new PHUIInfoView())
->setErrors($errors);
}
switch ($action) {
case ConpherenceUpdateActions::NOTIFICATIONS:
$dialog = $this->renderPreferencesDialog($conpherence);
break;
case ConpherenceUpdateActions::ADD_PERSON:
$dialog = $this->renderAddPersonDialog($conpherence);
break;
case ConpherenceUpdateActions::REMOVE_PERSON:
$dialog = $this->renderRemovePersonDialog($conpherence);
break;
case ConpherenceUpdateActions::METADATA:
default:
$dialog = $this->renderMetadataDialog($conpherence, $error_view);
break;
}
return
$dialog
->setUser($user)
->setWidth(AphrontDialogView::WIDTH_FORM)
->setSubmitURI($this->getApplicationURI('update/'.$conpherence_id.'/'))
->addSubmitButton()
->addCancelButton($this->getApplicationURI($conpherence->getID().'/'));
}
private function renderPreferencesDialog(
ConpherenceThread $conpherence) {
$request = $this->getRequest();
$user = $request->getUser();
$participant = $conpherence->getParticipantIfExists($user->getPHID());
if (!$participant) {
if ($user->isLoggedIn()) {
$text = pht(
'Notification settings are available after joining the room.');
} else {
$text = pht(
'Notification settings are available after logging in and joining '.
'the room.');
}
return id(new AphrontDialogView())
->setTitle(pht('Room Preferences'))
->appendParagraph($text);
}
$notification_key = PhabricatorConpherenceNotificationsSetting::SETTINGKEY;
$notification_default = $user->getUserSetting($notification_key);
$settings = $participant->getSettings();
$notifications = idx(
$settings,
'notifications',
$notification_default);
$form = id(new AphrontFormView())
->setUser($user)
->setFullWidth(true)
->appendControl(
id(new AphrontFormRadioButtonControl())
->addButton(
PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_EMAIL,
PhabricatorConpherenceNotificationsSetting::getSettingLabel(
PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_EMAIL),
'')
->addButton(
PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_NOTIFY,
PhabricatorConpherenceNotificationsSetting::getSettingLabel(
PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_NOTIFY),
'')
->setName('notifications')
->setValue($notifications));
return id(new AphrontDialogView())
->setTitle(pht('Room Preferences'))
->addHiddenInput('action', 'notifications')
->addHiddenInput(
'latest_transaction_id',
$request->getInt('latest_transaction_id'))
->appendForm($form);
}
private function renderAddPersonDialog(
ConpherenceThread $conpherence) {
$request = $this->getRequest();
$user = $request->getUser();
$add_person = $request->getStr('add_person');
$form = id(new AphrontFormView())
->setUser($user)
->setFullWidth(true)
->appendControl(
id(new AphrontFormTokenizerControl())
->setName('add_person')
->setUser($user)
->setDatasource(new PhabricatorPeopleDatasource()));
$view = id(new AphrontDialogView())
->setTitle(pht('Add Participants'))
->addHiddenInput('action', 'add_person')
->addHiddenInput(
'latest_transaction_id',
$request->getInt('latest_transaction_id'))
->appendForm($form);
return $view;
}
private function renderRemovePersonDialog(
ConpherenceThread $conpherence) {
$request = $this->getRequest();
$viewer = $request->getUser();
$remove_person = $request->getStr('remove_person');
$participants = $conpherence->getParticipants();
$removed_user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($remove_person))
->executeOne();
if (!$removed_user) {
return new Aphront404Response();
}
$is_self = ($viewer->getPHID() == $removed_user->getPHID());
$is_last = (count($participants) == 1);
$test_conpherence = clone $conpherence;
$test_conpherence->attachParticipants(array());
$still_visible = PhabricatorPolicyFilter::hasCapability(
$removed_user,
$test_conpherence,
PhabricatorPolicyCapability::CAN_VIEW);
$body = array();
if ($is_self) {
$title = pht('Leave Room');
$body[] = pht(
'Are you sure you want to leave this room?');
} else {
$title = pht('Banish User');
$body[] = pht(
'Banish %s from the realm?',
phutil_tag('strong', array(), $removed_user->getUsername()));
}
if ($still_visible) {
if ($is_self) {
$body[] = pht(
'You will be able to rejoin the room later.');
} else {
$body[] = pht(
'This user will be able to rejoin the room later.');
}
} else {
if ($is_self) {
if ($is_last) {
$body[] = pht(
'You are the last member, so you will never be able to rejoin '.
'the room.');
} else {
$body[] = pht(
'You will not be able to rejoin the room on your own, but '.
'someone else can invite you later.');
}
} else {
$body[] = pht(
'This user will not be able to rejoin the room unless invited '.
'again.');
}
}
$dialog = id(new AphrontDialogView())
->setTitle($title)
->addHiddenInput('action', 'remove_person')
->addHiddenInput('remove_person', $remove_person)
->addHiddenInput(
'latest_transaction_id',
$request->getInt('latest_transaction_id'))
->addHiddenInput('__continue__', true);
foreach ($body as $paragraph) {
$dialog->appendParagraph($paragraph);
}
return $dialog;
}
private function renderMetadataDialog(
ConpherenceThread $conpherence,
$error_view) {
$request = $this->getRequest();
$user = $request->getUser();
$title = pht('Update Room');
$form = id(new PHUIFormLayoutView())
->appendChild($error_view)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Title'))
->setName('title')
->setValue($conpherence->getTitle()))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Topic'))
->setName('topic')
->setValue($conpherence->getTopic()));
$policies = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($conpherence)
->execute();
$form
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($conpherence)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicies($policies))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($conpherence)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies));
$view = id(new AphrontDialogView())
->setTitle($title)
->addHiddenInput('action', 'metadata')
->addHiddenInput(
'latest_transaction_id',
$request->getInt('latest_transaction_id'))
->addHiddenInput('__continue__', true)
->appendChild($form);
if ($request->getExists('force_ajax')) {
$view->addHiddenInput('force_ajax', true);
}
return $view;
}
private function loadAndRenderUpdates(
$action,
$conpherence_id,
$latest_transaction_id) {
$need_transactions = false;
- $need_participant_cache = true;
switch ($action) {
case ConpherenceUpdateActions::METADATA:
case ConpherenceUpdateActions::LOAD:
$need_transactions = true;
break;
case ConpherenceUpdateActions::MESSAGE:
case ConpherenceUpdateActions::ADD_PERSON:
$need_transactions = true;
break;
case ConpherenceUpdateActions::REMOVE_PERSON:
case ConpherenceUpdateActions::NOTIFICATIONS:
default:
break;
}
$user = $this->getRequest()->getUser();
$conpherence = id(new ConpherenceThreadQuery())
->setViewer($user)
->setAfterTransactionID($latest_transaction_id)
->needProfileImage(true)
- ->needParticipantCache($need_participant_cache)
->needParticipants(true)
->needTransactions($need_transactions)
->withIDs(array($conpherence_id))
->executeOne();
$non_update = false;
if ($need_transactions && $conpherence->getTransactions()) {
$data = ConpherenceTransactionRenderer::renderTransactions(
$user,
$conpherence);
$key = PhabricatorConpherenceColumnMinimizeSetting::SETTINGKEY;
$minimized = $user->getUserSetting($key);
if (!$minimized) {
$participant_obj = $conpherence->getParticipant($user->getPHID());
$participant_obj
->markUpToDate($conpherence, $data['latest_transaction']);
}
} else if ($need_transactions) {
$non_update = true;
$data = array();
} else {
$data = array();
}
$rendered_transactions = idx($data, 'transactions');
$new_latest_transaction_id = idx($data, 'latest_transaction_id');
$update_uri = $this->getApplicationURI('update/'.$conpherence->getID().'/');
$nav_item = null;
$header = null;
$people_widget = null;
switch ($action) {
case ConpherenceUpdateActions::METADATA:
$policy_objects = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($conpherence)
->execute();
$header = $this->buildHeaderPaneContent(
$conpherence,
$policy_objects);
$header = hsprintf('%s', $header);
$nav_item = id(new ConpherenceThreadListView())
->setUser($user)
->setBaseURI($this->getApplicationURI())
->renderSingleThread($conpherence, $policy_objects);
$nav_item = hsprintf('%s', $nav_item);
break;
case ConpherenceUpdateActions::ADD_PERSON:
$people_widget = id(new ConpherenceParticipantView())
->setUser($user)
->setConpherence($conpherence)
->setUpdateURI($update_uri);
$people_widget = hsprintf('%s', $people_widget->render());
break;
case ConpherenceUpdateActions::REMOVE_PERSON:
case ConpherenceUpdateActions::NOTIFICATIONS:
default:
break;
}
$data = $conpherence->getDisplayData($user);
$dropdown_query = id(new AphlictDropdownDataQuery())
->setViewer($user);
$dropdown_query->execute();
$content = array(
'non_update' => $non_update,
'transactions' => hsprintf('%s', $rendered_transactions),
'conpherence_title' => (string)$data['title'],
'latest_transaction_id' => $new_latest_transaction_id,
'nav_item' => $nav_item,
'conpherence_phid' => $conpherence->getPHID(),
'header' => $header,
'people_widget' => $people_widget,
'aphlictDropdownData' => array(
$dropdown_query->getNotificationData(),
$dropdown_query->getConpherenceData(),
),
);
return $content;
}
}
diff --git a/src/applications/conpherence/controller/ConpherenceViewController.php b/src/applications/conpherence/controller/ConpherenceViewController.php
index 57fe194845..6b953b9de4 100644
--- a/src/applications/conpherence/controller/ConpherenceViewController.php
+++ b/src/applications/conpherence/controller/ConpherenceViewController.php
@@ -1,245 +1,244 @@
<?php
final class ConpherenceViewController extends
ConpherenceController {
const OLDER_FETCH_LIMIT = 5;
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$user = $request->getUser();
$conpherence_id = $request->getURIData('id');
if (!$conpherence_id) {
return new Aphront404Response();
}
$query = id(new ConpherenceThreadQuery())
->setViewer($user)
->withIDs(array($conpherence_id))
->needProfileImage(true)
- ->needParticipantCache(true)
->needTransactions(true)
->setTransactionLimit($this->getMainQueryLimit());
$before_transaction_id = $request->getInt('oldest_transaction_id');
$after_transaction_id = $request->getInt('newest_transaction_id');
$old_message_id = $request->getURIData('messageID');
if ($before_transaction_id && ($old_message_id || $after_transaction_id)) {
throw new Aphront400Response();
}
if ($old_message_id && $after_transaction_id) {
throw new Aphront400Response();
}
$marker_type = 'older';
if ($before_transaction_id) {
$query
->setBeforeTransactionID($before_transaction_id);
}
if ($old_message_id) {
$marker_type = 'olderandnewer';
$query
->setAfterTransactionID($old_message_id - 1);
}
if ($after_transaction_id) {
$marker_type = 'newer';
$query
->setAfterTransactionID($after_transaction_id);
}
$conpherence = $query->executeOne();
if (!$conpherence) {
return new Aphront404Response();
}
$this->setConpherence($conpherence);
$transactions = $this->getNeededTransactions(
$conpherence,
$old_message_id);
$latest_transaction = head($transactions);
$participant = $conpherence->getParticipantIfExists($user->getPHID());
if ($participant) {
if (!$participant->isUpToDate($conpherence)) {
$write_guard = AphrontWriteGuard::beginScopedUnguardedWrites();
$participant->markUpToDate($conpherence, $latest_transaction);
$user->clearCacheData(PhabricatorUserMessageCountCacheType::KEY_COUNT);
unset($write_guard);
}
}
$data = ConpherenceTransactionRenderer::renderTransactions(
$user,
$conpherence,
$marker_type);
$messages = ConpherenceTransactionRenderer::renderMessagePaneContent(
$data['transactions'],
$data['oldest_transaction_id'],
$data['newest_transaction_id']);
if ($before_transaction_id || $after_transaction_id) {
$header = null;
$form = null;
$content = array('transactions' => $messages);
} else {
$policy_objects = id(new PhabricatorPolicyQuery())
->setViewer($user)
->setObject($conpherence)
->execute();
$header = $this->buildHeaderPaneContent($conpherence, $policy_objects);
$search = $this->buildSearchForm();
$form = $this->renderFormContent();
$content = array(
'header' => $header,
'search' => $search,
'transactions' => $messages,
'form' => $form,
);
}
$d_data = $conpherence->getDisplayData($user);
$content['title'] = $title = $d_data['title'];
if ($request->isAjax()) {
$dropdown_query = id(new AphlictDropdownDataQuery())
->setViewer($user);
$dropdown_query->execute();
$content['threadID'] = $conpherence->getID();
$content['threadPHID'] = $conpherence->getPHID();
$content['latestTransactionID'] = $data['latest_transaction_id'];
$content['canEdit'] = PhabricatorPolicyFilter::hasCapability(
$user,
$conpherence,
PhabricatorPolicyCapability::CAN_EDIT);
$content['aphlictDropdownData'] = array(
$dropdown_query->getNotificationData(),
$dropdown_query->getConpherenceData(),
);
return id(new AphrontAjaxResponse())->setContent($content);
}
$layout = id(new ConpherenceLayoutView())
->setUser($user)
->setBaseURI($this->getApplicationURI())
->setThread($conpherence)
->setHeader($header)
->setSearch($search)
->setMessages($messages)
->setReplyForm($form)
->setLatestTransactionID($data['latest_transaction_id'])
->setRole('thread');
$participating = $conpherence->getParticipantIfExists($user->getPHID());
if (!$user->isLoggedIn()) {
$layout->addClass('conpherence-no-pontificate');
}
return $this->newPage()
->setTitle($title)
->setPageObjectPHIDs(array($conpherence->getPHID()))
->appendChild($layout);
}
private function renderFormContent() {
$conpherence = $this->getConpherence();
$user = $this->getRequest()->getUser();
$participating = $conpherence->getParticipantIfExists($user->getPHID());
if (!$participating && $user->isLoggedIn()) {
return null;
}
$draft = PhabricatorDraft::newFromUserAndKey(
$user,
$conpherence->getPHID());
$update_uri = $this->getApplicationURI('update/'.$conpherence->getID().'/');
if ($user->isLoggedIn()) {
$this->initBehavior('conpherence-pontificate');
if ($participating) {
$action = ConpherenceUpdateActions::MESSAGE;
$status = new PhabricatorNotificationStatusView();
} else {
$action = ConpherenceUpdateActions::JOIN_ROOM;
$status = pht('Sending a message will also join the room.');
}
$form = id(new AphrontFormView())
->setUser($user)
->setAction($update_uri)
->addSigil('conpherence-pontificate')
->setWorkflow(true)
->addHiddenInput('action', $action)
->appendChild(
id(new PhabricatorRemarkupControl())
->setUser($user)
->setName('text')
->setSendOnEnter(true)
->setValue($draft->getDraft()));
$status_view = phutil_tag(
'div',
array(
'class' => 'conpherence-room-status',
'id' => 'conpherence-room-status',
),
$status);
$view = phutil_tag_div(
'pontificate-container', array($form, $status_view));
return $view;
} else {
// user not logged in so give them a login button.
$login_href = id(new PhutilURI('/auth/start/'))
->setQueryParam('next', '/'.$conpherence->getMonogram());
return id(new PHUIFormLayoutView())
->addClass('login-to-participate')
->appendInstructions(pht('Log in to join this room and participate.'))
->appendChild(
id(new PHUIButtonView())
->setTag('a')
->setText(pht('Login to Participate'))
->setHref((string)$login_href));
}
}
private function getNeededTransactions(
ConpherenceThread $conpherence,
$message_id) {
if ($message_id) {
$newer_transactions = $conpherence->getTransactions();
$query = id(new ConpherenceTransactionQuery())
->setViewer($this->getRequest()->getUser())
->withObjectPHIDs(array($conpherence->getPHID()))
->setAfterID($message_id)
->needHandles(true)
->setLimit(self::OLDER_FETCH_LIMIT);
$older_transactions = $query->execute();
$handles = array();
foreach ($older_transactions as $transaction) {
$handles += $transaction->getHandles();
}
$conpherence->attachHandles($conpherence->getHandles() + $handles);
$transactions = array_merge($newer_transactions, $older_transactions);
$conpherence->attachTransactions($transactions);
} else {
$transactions = $conpherence->getTransactions();
}
return $transactions;
}
private function getMainQueryLimit() {
$request = $this->getRequest();
$base_limit = ConpherenceThreadQuery::TRANSACTION_LIMIT;
if ($request->getURIData('messageID')) {
$base_limit = $base_limit - self::OLDER_FETCH_LIMIT;
}
return $base_limit;
}
}
diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php
index e4728f3c00..e358700348 100644
--- a/src/applications/conpherence/editor/ConpherenceEditor.php
+++ b/src/applications/conpherence/editor/ConpherenceEditor.php
@@ -1,556 +1,517 @@
<?php
final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
const ERROR_EMPTY_PARTICIPANTS = 'error-empty-participants';
const ERROR_EMPTY_MESSAGE = 'error-empty-message';
public function getEditorApplicationClass() {
return 'PhabricatorConpherenceApplication';
}
public function getEditorObjectsDescription() {
return pht('Conpherence Rooms');
}
public static function createThread(
PhabricatorUser $creator,
array $participant_phids,
$title,
$message,
PhabricatorContentSource $source,
$topic) {
$conpherence = ConpherenceThread::initializeNewRoom($creator);
$errors = array();
if (empty($participant_phids)) {
$errors[] = self::ERROR_EMPTY_PARTICIPANTS;
} else {
$participant_phids[] = $creator->getPHID();
$participant_phids = array_unique($participant_phids);
}
if (empty($message)) {
$errors[] = self::ERROR_EMPTY_MESSAGE;
}
if (!$errors) {
$xactions = array();
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(ConpherenceTransaction::TYPE_PARTICIPANTS)
->setNewValue(array('+' => $participant_phids));
if ($title) {
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(
ConpherenceThreadTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title);
}
if (strlen($topic)) {
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(
ConpherenceThreadTopicTransaction::TRANSACTIONTYPE)
->setNewValue($topic);
}
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new ConpherenceTransactionComment())
->setContent($message)
->setConpherencePHID($conpherence->getPHID()));
id(new ConpherenceEditor())
->setActor($creator)
->setContentSource($source)
->setContinueOnNoEffect(true)
->applyTransactions($conpherence, $xactions);
}
return array($errors, $conpherence);
}
public function generateTransactionsFromText(
PhabricatorUser $viewer,
ConpherenceThread $conpherence,
$text) {
$xactions = array();
$xactions[] = id(new ConpherenceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new ConpherenceTransactionComment())
->setContent($text)
->setConpherencePHID($conpherence->getPHID()));
return $xactions;
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = ConpherenceTransaction::TYPE_PARTICIPANTS;
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this room.', $author);
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case ConpherenceTransaction::TYPE_PARTICIPANTS:
if ($this->getIsNewObject()) {
return array();
}
return $object->getParticipantPHIDs();
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case ConpherenceTransaction::TYPE_PARTICIPANTS:
return $this->getPHIDTransactionNewValue($xaction);
}
}
/**
* We really only need a read lock if we have a comment. In that case, we
* must update the messagesCount field on the conpherence and
* seenMessagesCount(s) for the participant(s).
*/
protected function shouldReadLock(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
$lock = false;
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$lock = true;
break;
}
return $lock;
}
/**
* We need to apply initial effects IFF the conpherence is new. We must
* save the conpherence first thing to make sure we have an id and a phid, as
* well as create the initial set of participants so that we pass policy
* checks.
*/
protected function shouldApplyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return $this->getIsNewObject();
}
protected function applyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
$object->save();
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case ConpherenceTransaction::TYPE_PARTICIPANTS:
// Since this is a new ConpherenceThread, we have to create the
// participation data asap to pass policy checks. For existing
// ConpherenceThreads, the existing participation is correct
// at this stage. Note that later in applyCustomExternalTransaction
// this participation data will be updated, particularly the
// behindTransactionPHID which is just a generated dummy for now.
$participants = array();
$phids = $this->getPHIDTransactionNewValue($xaction, array());
foreach ($phids as $phid) {
if ($phid == $this->getActor()->getPHID()) {
$status = ConpherenceParticipationStatus::UP_TO_DATE;
$message_count = 1;
} else {
$status = ConpherenceParticipationStatus::BEHIND;
$message_count = 0;
}
$participants[$phid] =
id(new ConpherenceParticipant())
->setConpherencePHID($object->getPHID())
->setParticipantPHID($phid)
->setParticipationStatus($status)
->setDateTouched(time())
->setBehindTransactionPHID($xaction->generatePHID())
->setSeenMessageCount($message_count)
->save();
$object->attachParticipants($participants);
- $object->setRecentParticipantPHIDs(array_keys($participants));
}
break;
}
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
- $make_author_recent_participant = true;
switch ($xaction->getTransactionType()) {
case ConpherenceTransaction::TYPE_PARTICIPANTS:
- if (!$this->getIsNewObject()) {
- $old_map = array_fuse($xaction->getOldValue());
- $new_map = array_fuse($xaction->getNewValue());
- // if we added people, add them to the end of "recent" participants
- $add = array_keys(array_diff_key($new_map, $old_map));
- // if we remove people, then definintely remove them from "recent"
- // participants
- $del = array_keys(array_diff_key($old_map, $new_map));
- if ($add || $del) {
- $participants = $object->getRecentParticipantPHIDs();
- if ($add) {
- $participants = array_merge($participants, $add);
- }
- if ($del) {
- $participants = array_diff($participants, $del);
- $actor = $this->requireActor();
- if (in_array($actor->getPHID(), $del)) {
- $make_author_recent_participant = false;
- }
- }
- $participants = array_slice(array_unique($participants), 0, 10);
- $object->setRecentParticipantPHIDs($participants);
- }
- }
+ if (!$this->getIsNewObject()) {}
break;
}
- if ($make_author_recent_participant) {
- $this->makeAuthorMostRecentParticipant($object, $xaction);
- }
}
protected function applyBuiltinInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$object->setMessageCount((int)$object->getMessageCount() + 1);
break;
}
return parent::applyBuiltinInternalTransaction($object, $xaction);
}
- private function makeAuthorMostRecentParticipant(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- $participants = $object->getRecentParticipantPHIDs();
- array_unshift($participants, $xaction->getAuthorPHID());
- $participants = array_slice(array_unique($participants), 0, 10);
-
- $object->setRecentParticipantPHIDs($participants);
- }
-
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case ConpherenceTransaction::TYPE_PARTICIPANTS:
if ($this->getIsNewObject()) {
continue;
}
$participants = $object->getParticipants();
$old_map = array_fuse($xaction->getOldValue());
$new_map = array_fuse($xaction->getNewValue());
$remove = array_keys(array_diff_key($old_map, $new_map));
foreach ($remove as $phid) {
$remove_participant = $participants[$phid];
$remove_participant->delete();
unset($participants[$phid]);
}
$add = array_keys(array_diff_key($new_map, $old_map));
foreach ($add as $phid) {
if ($phid == $this->getActor()->getPHID()) {
$status = ConpherenceParticipationStatus::UP_TO_DATE;
$message_count = $object->getMessageCount();
} else {
$status = ConpherenceParticipationStatus::BEHIND;
$message_count = 0;
}
$participants[$phid] =
id(new ConpherenceParticipant())
->setConpherencePHID($object->getPHID())
->setParticipantPHID($phid)
->setParticipationStatus($status)
->setDateTouched(time())
->setBehindTransactionPHID($xaction->getPHID())
->setSeenMessageCount($message_count)
->save();
}
$object->attachParticipants($participants);
break;
}
}
protected function applyFinalEffects(
PhabricatorLiskDAO $object,
array $xactions) {
if (!$xactions) {
return $xactions;
}
$message_count = 0;
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$message_count++;
// update everyone's participation status on a message -only-
$xaction_phid = $xaction->getPHID();
$behind = ConpherenceParticipationStatus::BEHIND;
$up_to_date = ConpherenceParticipationStatus::UP_TO_DATE;
$participants = $object->getParticipants();
$user = $this->getActor();
$time = time();
foreach ($participants as $phid => $participant) {
if ($phid != $user->getPHID()) {
if ($participant->getParticipationStatus() != $behind) {
$participant->setBehindTransactionPHID($xaction_phid);
$participant->setSeenMessageCount(
$object->getMessageCount() - $message_count);
}
$participant->setParticipationStatus($behind);
$participant->setDateTouched($time);
} else {
$participant->setSeenMessageCount($object->getMessageCount());
$participant->setBehindTransactionPHID($xaction_phid);
$participant->setParticipationStatus($up_to_date);
$participant->setDateTouched($time);
}
$participant->save();
}
PhabricatorUserCache::clearCaches(
PhabricatorUserMessageCountCacheType::KEY_COUNT,
array_keys($participants));
break;
}
}
if ($xactions) {
$data = array(
'type' => 'message',
'threadPHID' => $object->getPHID(),
'messageID' => last($xactions)->getID(),
'subscribers' => array($object->getPHID()),
);
PhabricatorNotificationClient::tryToPostMessage($data);
}
return $xactions;
}
protected function requireCapabilities(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
parent::requireCapabilities($object, $xaction);
switch ($xaction->getTransactionType()) {
case ConpherenceTransaction::TYPE_PARTICIPANTS:
$old_map = array_fuse($xaction->getOldValue());
$new_map = array_fuse($xaction->getNewValue());
$add = array_keys(array_diff_key($new_map, $old_map));
$rem = array_keys(array_diff_key($old_map, $new_map));
$actor_phid = $this->requireActor()->getPHID();
// You need CAN_EDIT to change participants other than yourself.
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_EDIT);
break;
case ConpherenceThreadTitleTransaction::TRANSACTIONTYPE:
case ConpherenceThreadTopicTransaction::TRANSACTIONTYPE:
PhabricatorPolicyFilter::requireCapability(
$this->requireActor(),
$object,
PhabricatorPolicyCapability::CAN_EDIT);
break;
}
}
protected function mergeTransactions(
PhabricatorApplicationTransaction $u,
PhabricatorApplicationTransaction $v) {
$type = $u->getTransactionType();
switch ($type) {
case ConpherenceTransaction::TYPE_PARTICIPANTS:
return $this->mergePHIDOrEdgeTransactions($u, $v);
}
return parent::mergeTransactions($u, $v);
}
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new ConpherenceReplyHandler())
->setActor($this->getActor())
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$title = $object->getTitle();
if (!$title) {
$title = pht(
'%s sent you a message.',
$this->getActor()->getUserName());
}
$phid = $object->getPHID();
return id(new PhabricatorMetaMTAMail())
->setSubject("Z{$id}: {$title}")
->addHeader('Thread-Topic', "Z{$id}: {$phid}");
}
protected function getMailTo(PhabricatorLiskDAO $object) {
$to_phids = array();
$participants = $object->getParticipants();
if (!$participants) {
return $to_phids;
}
$participant_phids = mpull($participants, 'getParticipantPHID');
$users = id(new PhabricatorPeopleQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs($participant_phids)
->needUserSettings(true)
->execute();
$users = mpull($users, null, 'getPHID');
$notification_key = PhabricatorConpherenceNotificationsSetting::SETTINGKEY;
$notification_email =
PhabricatorConpherenceNotificationsSetting::VALUE_CONPHERENCE_EMAIL;
foreach ($participants as $phid => $participant) {
$user = idx($users, $phid);
if ($user) {
$default = $user->getUserSetting($notification_key);
} else {
$default = $notification_email;
}
$settings = $participant->getSettings();
$notifications = idx($settings, 'notifications', $default);
if ($notifications == $notification_email) {
$to_phids[] = $phid;
}
}
return $to_phids;
}
protected function getMailCC(PhabricatorLiskDAO $object) {
return array();
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
$body->addLinkSection(
pht('CONPHERENCE DETAIL'),
PhabricatorEnv::getProductionURI('/'.$object->getMonogram()));
return $body;
}
protected function addEmailPreferenceSectionToMailBody(
PhabricatorMetaMTAMailBody $body,
PhabricatorLiskDAO $object,
array $xactions) {
$href = PhabricatorEnv::getProductionURI(
'/'.$object->getMonogram().'?settings');
$label = pht('EMAIL PREFERENCES FOR THIS ROOM');
$body->addLinkSection($label, $href);
}
protected function getMailSubjectPrefix() {
return PhabricatorEnv::getEnvConfig('metamta.conpherence.subject-prefix');
}
protected function supportsSearch() {
return true;
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case ConpherenceTransaction::TYPE_PARTICIPANTS:
foreach ($xactions as $xaction) {
$new_phids = $this->getPHIDTransactionNewValue($xaction, array());
$old_phids = nonempty($object->getParticipantPHIDs(), array());
$phids = array_diff($new_phids, $old_phids);
if (!$phids) {
continue;
}
$users = id(new PhabricatorPeopleQuery())
->setViewer($this->requireActor())
->withPHIDs($phids)
->execute();
$users = mpull($users, null, 'getPHID');
foreach ($phids as $phid) {
if (isset($users[$phid])) {
continue;
}
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('New room participant "%s" is not a valid user.', $phid),
$xaction);
}
}
break;
}
return $errors;
}
}
diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php
index 48dc3323e0..1bb96537dc 100644
--- a/src/applications/conpherence/query/ConpherenceThreadQuery.php
+++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php
@@ -1,335 +1,326 @@
<?php
final class ConpherenceThreadQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
const TRANSACTION_LIMIT = 100;
private $phids;
private $ids;
private $participantPHIDs;
private $needParticipants;
private $needTransactions;
- private $needParticipantCache;
private $afterTransactionID;
private $beforeTransactionID;
private $transactionLimit;
private $fulltext;
private $needProfileImage;
- public function needParticipantCache($participant_cache) {
- $this->needParticipantCache = $participant_cache;
- return $this;
- }
-
public function needParticipants($need) {
$this->needParticipants = $need;
return $this;
}
public function needProfileImage($need) {
$this->needProfileImage = $need;
return $this;
}
public function needTransactions($need_transactions) {
$this->needTransactions = $need_transactions;
return $this;
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withParticipantPHIDs(array $phids) {
$this->participantPHIDs = $phids;
return $this;
}
public function setAfterTransactionID($id) {
$this->afterTransactionID = $id;
return $this;
}
public function setBeforeTransactionID($id) {
$this->beforeTransactionID = $id;
return $this;
}
public function setTransactionLimit($transaction_limit) {
$this->transactionLimit = $transaction_limit;
return $this;
}
public function getTransactionLimit() {
return $this->transactionLimit;
}
public function withFulltext($query) {
$this->fulltext = $query;
return $this;
}
public function withTitleNgrams($ngrams) {
return $this->withNgramsConstraint(
id(new ConpherenceThreadTitleNgrams()),
$ngrams);
}
protected function loadPage() {
$table = new ConpherenceThread();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT thread.* FROM %T thread %Q %Q %Q %Q %Q',
$table->getTableName(),
$this->buildJoinClause($conn_r),
$this->buildWhereClause($conn_r),
$this->buildGroupClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
$conpherences = $table->loadAllFromArray($data);
if ($conpherences) {
$conpherences = mpull($conpherences, null, 'getPHID');
$this->loadParticipantsAndInitHandles($conpherences);
- if ($this->needParticipantCache) {
- $this->loadCoreHandles($conpherences, 'getRecentParticipantPHIDs');
- }
if ($this->needParticipants) {
$this->loadCoreHandles($conpherences, 'getParticipantPHIDs');
}
if ($this->needTransactions) {
$this->loadTransactionsAndHandles($conpherences);
}
if ($this->needProfileImage) {
$default = null;
$file_phids = mpull($conpherences, 'getProfileImagePHID');
$file_phids = array_filter($file_phids);
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
$files = array();
}
foreach ($conpherences as $conpherence) {
$file = idx($files, $conpherence->getProfileImagePHID());
if (!$file) {
if (!$default) {
$default = PhabricatorFile::loadBuiltin(
$this->getViewer(),
'conpherence.png');
}
$file = $default;
}
$conpherence->attachProfileImageFile($file);
}
}
}
return $conpherences;
}
protected function buildGroupClause(AphrontDatabaseConnection $conn_r) {
if ($this->participantPHIDs !== null || strlen($this->fulltext)) {
return 'GROUP BY thread.id';
} else {
return $this->buildApplicationSearchGroupClause($conn_r);
}
}
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->participantPHIDs !== null) {
$joins[] = qsprintf(
$conn,
'JOIN %T p ON p.conpherencePHID = thread.phid',
id(new ConpherenceParticipant())->getTableName());
}
if (strlen($this->fulltext)) {
$joins[] = qsprintf(
$conn,
'JOIN %T idx ON idx.threadPHID = thread.phid',
id(new ConpherenceIndex())->getTableName());
}
// See note in buildWhereClauseParts() about this optimization.
$viewer = $this->getViewer();
if (!$viewer->isOmnipotent() && $viewer->isLoggedIn()) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T vp ON vp.conpherencePHID = thread.phid
AND vp.participantPHID = %s',
id(new ConpherenceParticipant())->getTableName(),
$viewer->getPHID());
}
return $joins;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
// Optimize policy filtering of private rooms. If we are not looking for
// particular rooms by ID or PHID, we can just skip over any rooms with
// "View Policy: Room Participants" if the viewer isn't a participant: we
// know they won't be able to see the room.
// This avoids overheating browse/search queries, since it's common for
// a large number of rooms to be private and have this view policy.
$viewer = $this->getViewer();
$can_optimize =
!$viewer->isOmnipotent() &&
($this->ids === null) &&
($this->phids === null);
if ($can_optimize) {
$members_policy = id(new ConpherenceThreadMembersPolicyRule())
->getObjectPolicyFullKey();
if ($viewer->isLoggedIn()) {
$where[] = qsprintf(
$conn,
'thread.viewPolicy != %s OR vp.participantPHID = %s',
$members_policy,
$viewer->getPHID());
} else {
$where[] = qsprintf(
$conn,
'thread.viewPolicy != %s',
$members_policy);
}
}
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'thread.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'thread.phid IN (%Ls)',
$this->phids);
}
if ($this->participantPHIDs !== null) {
$where[] = qsprintf(
$conn,
'p.participantPHID IN (%Ls)',
$this->participantPHIDs);
}
if (strlen($this->fulltext)) {
$where[] = qsprintf(
$conn,
'MATCH(idx.corpus) AGAINST (%s IN BOOLEAN MODE)',
$this->fulltext);
}
return $where;
}
private function loadParticipantsAndInitHandles(array $conpherences) {
$participants = id(new ConpherenceParticipant())
->loadAllWhere('conpherencePHID IN (%Ls)', array_keys($conpherences));
$map = mgroup($participants, 'getConpherencePHID');
foreach ($conpherences as $current_conpherence) {
$conpherence_phid = $current_conpherence->getPHID();
$conpherence_participants = idx(
$map,
$conpherence_phid,
array());
$conpherence_participants = mpull(
$conpherence_participants,
null,
'getParticipantPHID');
$current_conpherence->attachParticipants($conpherence_participants);
$current_conpherence->attachHandles(array());
}
return $this;
}
private function loadCoreHandles(
array $conpherences,
$method) {
$handle_phids = array();
foreach ($conpherences as $conpherence) {
$handle_phids[$conpherence->getPHID()] =
$conpherence->$method();
}
$flat_phids = array_mergev($handle_phids);
$viewer = $this->getViewer();
$handles = $viewer->loadHandles($flat_phids);
$handles = iterator_to_array($handles);
foreach ($handle_phids as $conpherence_phid => $phids) {
$conpherence = $conpherences[$conpherence_phid];
$conpherence->attachHandles(
$conpherence->getHandles() + array_select_keys($handles, $phids));
}
return $this;
}
private function loadTransactionsAndHandles(array $conpherences) {
$query = id(new ConpherenceTransactionQuery())
->setViewer($this->getViewer())
->withObjectPHIDs(array_keys($conpherences))
->needHandles(true);
// We have to flip these for the underyling query class. The semantics of
// paging are tricky business.
if ($this->afterTransactionID) {
$query->setBeforeID($this->afterTransactionID);
} else if ($this->beforeTransactionID) {
$query->setAfterID($this->beforeTransactionID);
}
if ($this->getTransactionLimit()) {
// fetch an extra for "show older" scenarios
$query->setLimit($this->getTransactionLimit() + 1);
}
$transactions = $query->execute();
$transactions = mgroup($transactions, 'getObjectPHID');
foreach ($conpherences as $phid => $conpherence) {
$current_transactions = idx($transactions, $phid, array());
$handles = array();
foreach ($current_transactions as $transaction) {
$handles += $transaction->getHandles();
}
$conpherence->attachHandles($conpherence->getHandles() + $handles);
$conpherence->attachTransactions($current_transactions);
}
return $this;
}
public function getQueryApplicationClass() {
return 'PhabricatorConpherenceApplication';
}
protected function getPrimaryTableAlias() {
return 'thread';
}
}
diff --git a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php
index 4e0a89d266..cbaf43b0a9 100644
--- a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php
+++ b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php
@@ -1,454 +1,445 @@
<?php
final class ConpherenceThreadSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Conpherence Rooms');
}
public function getApplicationClassName() {
return 'PhabricatorConpherenceApplication';
}
public function newQuery() {
return id(new ConpherenceThreadQuery())
- ->needParticipantCache(true)
->needProfileImage(true);
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorUsersSearchField())
->setLabel(pht('Participants'))
->setKey('participants')
->setAliases(array('participant')),
id(new PhabricatorSearchDatasourceField())
->setLabel(pht('Rooms'))
->setKey('phids')
->setDescription(pht('Search by room titles.'))
->setDatasource(id(new ConpherenceThreadDatasource())),
id(new PhabricatorSearchTextField())
->setLabel(pht('Room Contains Words'))
->setKey('fulltext'),
);
}
protected function getDefaultFieldOrder() {
return array(
'participants',
'...',
);
}
protected function shouldShowOrderField() {
return false;
}
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['participants']) {
$query->withParticipantPHIDs($map['participants']);
}
if ($map['fulltext']) {
$query->withFulltext($map['fulltext']);
}
if ($map['phids']) {
$query->withPHIDs($map['phids']);
}
return $query;
}
protected function getURI($path) {
return '/conpherence/search/'.$path;
}
protected function getBuiltinQueryNames() {
$names = array();
$names['all'] = pht('All Rooms');
if ($this->requireViewer()->isLoggedIn()) {
$names['participant'] = pht('Joined Rooms');
}
return $names;
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'all':
return $query;
case 'participant':
return $query->setParameter(
'participants',
array($this->requireViewer()->getPHID()));
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
- protected function getRequiredHandlePHIDsForResultList(
- array $conpherences,
- PhabricatorSavedQuery $query) {
-
- $recent = mpull($conpherences, 'getRecentParticipantPHIDs');
- return array_unique(array_mergev($recent));
- }
-
protected function renderResultList(
array $conpherences,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($conpherences, 'ConpherenceThread');
$viewer = $this->requireViewer();
$policy_objects = ConpherenceThread::loadViewPolicyObjects(
$viewer,
$conpherences);
$engines = array();
$fulltext = $query->getParameter('fulltext');
if (strlen($fulltext) && $conpherences) {
$context = $this->loadContextMessages($conpherences, $fulltext);
$author_phids = array();
foreach ($context as $phid => $messages) {
$conpherence = $conpherences[$phid];
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer)
->setContextObject($conpherence);
foreach ($messages as $group) {
foreach ($group as $message) {
$xaction = $message['xaction'];
if ($xaction) {
$author_phids[] = $xaction->getAuthorPHID();
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
}
$engine->process();
$engines[$phid] = $engine;
}
$handles = $viewer->loadHandles($author_phids);
$handles = iterator_to_array($handles);
} else {
$context = array();
}
$content = array();
$list = new PHUIObjectItemListView();
$list->setUser($viewer);
foreach ($conpherences as $conpherence_phid => $conpherence) {
$created = phabricator_date($conpherence->getDateCreated(), $viewer);
- $title = $conpherence->getDisplayTitle($viewer);
+ $title = $conpherence->getTitle();
$monogram = $conpherence->getMonogram();
$icon_name = $conpherence->getPolicyIconName($policy_objects);
$icon = id(new PHUIIconView())
->setIcon($icon_name);
if (!strlen($fulltext)) {
$item = id(new PHUIObjectItemView())
->setObjectName($conpherence->getMonogram())
->setHeader($title)
->setHref('/'.$conpherence->getMonogram())
->setObject($conpherence)
->setImageURI($conpherence->getProfileImageURI())
->addIcon('none', $created)
->addIcon(
'none',
pht('Messages: %d', $conpherence->getMessageCount()))
->addAttribute(
array(
$icon,
' ',
pht(
'Last updated %s',
phabricator_datetime($conpherence->getDateModified(), $viewer)),
));
$list->addItem($item);
} else {
$messages = idx($context, $conpherence_phid);
$box = array();
$list = null;
if ($messages) {
foreach ($messages as $group) {
$rows = array();
foreach ($group as $message) {
$xaction = $message['xaction'];
if (!$xaction) {
continue;
}
$view = id(new ConpherenceTransactionView())
->setUser($viewer)
->setHandles($handles)
->setMarkupEngine($engines[$conpherence_phid])
->setConpherenceThread($conpherence)
->setConpherenceTransaction($xaction)
->setSearchResult(true)
->addClass('conpherence-fulltext-result');
if ($message['match']) {
$view->addClass('conpherence-fulltext-match');
}
$rows[] = $view;
}
$box[] = id(new PHUIBoxView())
->appendChild($rows)
->addClass('conpherence-fulltext-results');
}
}
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon($icon_name)
->setHref('/'.$monogram);
$content[] = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($box);
}
}
if ($list) {
$content = $list;
} else {
$content = id(new PHUIBoxView())
->addClass('conpherence-search-room-results')
->appendChild($content);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setContent($content);
$result->setNoDataString(pht('No results found.'));
return $result;
}
private function loadContextMessages(array $threads, $fulltext) {
$phids = mpull($threads, 'getPHID');
// We want to load a few messages for each thread in the result list, to
// show some of the actual content hits to help the user find what they
// are looking for.
// This method is trying to batch this lookup in most cases, so we do
// between one and "a handful" of queries instead of one per thread in
// most cases. To do this:
//
// - Load a big block of results for all of the threads.
// - If we didn't get a full block back, we have everything that matches
// the query. Sort it out and exit.
// - Otherwise, some threads had a ton of hits, so we might not be
// getting everything we want (we could be getting back 1,000 hits for
// the first thread). Remove any threads which we have enough results
// for and try again.
// - Repeat until we have everything or every thread has enough results.
//
// In the worst case, we could end up degrading to one query per thread,
// but this is incredibly unlikely on real data.
// Size of the result blocks we're going to load.
$limit = 1000;
// Number of messages we want for each thread.
$want = 3;
$need = $phids;
$hits = array();
while ($need) {
$rows = id(new ConpherenceFulltextQuery())
->withThreadPHIDs($need)
->withFulltext($fulltext)
->setLimit($limit)
->execute();
foreach ($rows as $row) {
$hits[$row['threadPHID']][] = $row;
}
if (count($rows) < $limit) {
break;
}
foreach ($need as $key => $phid) {
if (count($hits[$phid]) >= $want) {
unset($need[$key]);
}
}
}
// Now that we have all the fulltext matches, throw away any extras that we
// aren't going to render so we don't need to do lookups on them.
foreach ($hits as $phid => $rows) {
if (count($rows) > $want) {
$hits[$phid] = array_slice($rows, 0, $want);
}
}
// For each fulltext match, we want to render a message before and after
// the match to give it some context. We already know the transactions
// before each match because the rows have a "previousTransactionPHID",
// but we need to do one more query to figure out the transactions after
// each match.
// Collect the transactions we want to find the next transactions for.
$after = array();
foreach ($hits as $phid => $rows) {
foreach ($rows as $row) {
$after[] = $row['transactionPHID'];
}
}
// Look up the next transactions.
if ($after) {
$after_rows = id(new ConpherenceFulltextQuery())
->withPreviousTransactionPHIDs($after)
->execute();
} else {
$after_rows = array();
}
// Build maps from PHIDs to the previous and next PHIDs.
$prev_map = array();
$next_map = array();
foreach ($after_rows as $row) {
$next_map[$row['previousTransactionPHID']] = $row['transactionPHID'];
}
foreach ($hits as $phid => $rows) {
foreach ($rows as $row) {
$prev = $row['previousTransactionPHID'];
if ($prev) {
$prev_map[$row['transactionPHID']] = $prev;
$next_map[$prev] = $row['transactionPHID'];
}
}
}
// Now we're going to collect the actual transaction PHIDs, in order, that
// we want to show for each thread.
$groups = array();
foreach ($hits as $thread_phid => $rows) {
$rows = ipull($rows, null, 'transactionPHID');
$done = array();
foreach ($rows as $phid => $row) {
if (isset($done[$phid])) {
continue;
}
$done[$phid] = true;
$group = array();
// Walk backward, finding all the previous results. We can just keep
// going until we run out of results because we've only loaded things
// that we want to show.
$prev = $phid;
while (true) {
if (!isset($prev_map[$prev])) {
// No previous transaction, so we're done.
break;
}
$prev = $prev_map[$prev];
if (isset($rows[$prev])) {
$match = true;
$done[$prev] = true;
} else {
$match = false;
}
$group[] = array(
'phid' => $prev,
'match' => $match,
);
}
if (count($group) > 1) {
$group = array_reverse($group);
}
$group[] = array(
'phid' => $phid,
'match' => true,
);
$next = $phid;
while (true) {
if (!isset($next_map[$next])) {
break;
}
$next = $next_map[$next];
if (isset($rows[$next])) {
$match = true;
$done[$next] = true;
} else {
$match = false;
}
$group[] = array(
'phid' => $next,
'match' => $match,
);
}
$groups[$thread_phid][] = $group;
}
}
// Load all the actual transactions we need.
$xaction_phids = array();
foreach ($groups as $thread_phid => $group) {
foreach ($group as $list) {
foreach ($list as $item) {
$xaction_phids[] = $item['phid'];
}
}
}
if ($xaction_phids) {
$xactions = id(new ConpherenceTransactionQuery())
->setViewer($this->requireViewer())
->withPHIDs($xaction_phids)
->needComments(true)
->execute();
$xactions = mpull($xactions, null, 'getPHID');
} else {
$xactions = array();
}
foreach ($groups as $thread_phid => $group) {
foreach ($group as $key => $list) {
foreach ($list as $lkey => $item) {
$xaction = idx($xactions, $item['phid']);
if ($xaction->shouldHide()) {
continue;
}
$groups[$thread_phid][$key][$lkey]['xaction'] = $xaction;
}
}
}
// TODO: Sort the groups chronologically?
return $groups;
}
}
diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php
index db6d4a7e61..f0d0e60bb3 100644
--- a/src/applications/conpherence/storage/ConpherenceThread.php
+++ b/src/applications/conpherence/storage/ConpherenceThread.php
@@ -1,416 +1,346 @@
<?php
final class ConpherenceThread extends ConpherenceDAO
implements
PhabricatorPolicyInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorMentionableInterface,
PhabricatorDestructibleInterface,
PhabricatorNgramsInterface {
protected $title;
protected $topic;
protected $profileImagePHID;
protected $messageCount;
- protected $recentParticipantPHIDs = array();
protected $mailKey;
protected $viewPolicy;
protected $editPolicy;
protected $joinPolicy;
private $participants = self::ATTACHABLE;
private $transactions = self::ATTACHABLE;
private $profileImageFile = self::ATTACHABLE;
private $handles = self::ATTACHABLE;
public static function initializeNewRoom(PhabricatorUser $sender) {
$default_policy = id(new ConpherenceThreadMembersPolicyRule())
->getObjectPolicyFullKey();
return id(new ConpherenceThread())
->setMessageCount(0)
->setTitle('')
->setTopic('')
->attachParticipants(array())
->setViewPolicy($default_policy)
->setEditPolicy($default_policy)
->setJoinPolicy('');
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
- self::CONFIG_SERIALIZATION => array(
- 'recentParticipantPHIDs' => self::SERIALIZATION_JSON,
- ),
self::CONFIG_COLUMN_SCHEMA => array(
'title' => 'text255?',
'topic' => 'text255',
'messageCount' => 'uint64',
'mailKey' => 'text20',
'joinPolicy' => 'policy',
'profileImagePHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorConpherenceThreadPHIDType::TYPECONST);
}
public function save() {
if (!$this->getMailKey()) {
$this->setMailKey(Filesystem::readRandomCharacters(20));
}
return parent::save();
}
public function getMonogram() {
return 'Z'.$this->getID();
}
public function attachParticipants(array $participants) {
assert_instances_of($participants, 'ConpherenceParticipant');
$this->participants = $participants;
return $this;
}
public function getParticipants() {
return $this->assertAttached($this->participants);
}
public function getParticipant($phid) {
$participants = $this->getParticipants();
return $participants[$phid];
}
public function getParticipantIfExists($phid, $default = null) {
$participants = $this->getParticipants();
return idx($participants, $phid, $default);
}
public function getParticipantPHIDs() {
$participants = $this->getParticipants();
return array_keys($participants);
}
public function attachHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function getHandles() {
return $this->assertAttached($this->handles);
}
public function attachTransactions(array $transactions) {
assert_instances_of($transactions, 'ConpherenceTransaction');
$this->transactions = $transactions;
return $this;
}
public function getTransactions($assert_attached = true) {
return $this->assertAttached($this->transactions);
}
public function hasAttachedTransactions() {
return $this->transactions !== self::ATTACHABLE;
}
public function getTransactionsFrom($begin = 0, $amount = null) {
$length = count($this->transactions);
return array_slice(
$this->getTransactions(),
$length - $begin - $amount,
$amount);
}
public function getProfileImageURI() {
return $this->getProfileImageFile()->getBestURI();
}
public function attachProfileImageFile(PhabricatorFile $file) {
$this->profileImageFile = $file;
return $this;
}
public function getProfileImageFile() {
return $this->assertAttached($this->profileImageFile);
}
/**
* Get a thread title which doesn't require handles to be attached.
*
* This is a less rich title than @{method:getDisplayTitle}, but does not
* require handles to be attached. We use it to build thread handles without
* risking cycles or recursion while querying.
*
* @return string Lower quality human-readable title.
*/
public function getStaticTitle() {
$title = $this->getTitle();
if (strlen($title)) {
return $title;
}
return pht('Private Room');
}
- /**
- * Get the thread's display title for a user.
- *
- * If a thread doesn't have a title set, this will return a string describing
- * recent participants.
- *
- * @param PhabricatorUser Viewer.
- * @return string Thread title.
- */
- public function getDisplayTitle(PhabricatorUser $viewer) {
- $title = $this->getTitle();
- if (strlen($title)) {
- return $title;
- }
-
- return $this->getRecentParticipantsString($viewer);
- }
-
-
- /**
- * Get recent participants (other than the viewer) as a string.
- *
- * For example, this method might return "alincoln, htaft, gwashington...".
- *
- * @param PhabricatorUser Viewer.
- * @return string Description of other participants.
- */
- private function getRecentParticipantsString(PhabricatorUser $viewer) {
- $handles = $this->getHandles();
- $phids = $this->getOtherRecentParticipantPHIDs($viewer);
-
- if (count($phids) == 0) {
- $phids[] = $viewer->getPHID();
- $more = false;
- } else {
- $limit = 3;
- $more = (count($phids) > $limit);
- $phids = array_slice($phids, 0, $limit);
- }
-
- $names = array_select_keys($handles, $phids);
- $names = mpull($names, 'getName');
- $names = implode(', ', $names);
-
- if ($more) {
- $names = $names.'...';
- }
-
- return $names;
- }
-
-
- /**
- * Get PHIDs for recent participants who are not the viewer.
- *
- * @param PhabricatorUser Viewer.
- * @return list<phid> Participants who are not the viewer.
- */
- private function getOtherRecentParticipantPHIDs(PhabricatorUser $viewer) {
- $phids = $this->getRecentParticipantPHIDs();
- $phids = array_fuse($phids);
- unset($phids[$viewer->getPHID()]);
- return array_values($phids);
- }
-
-
public function getDisplayData(PhabricatorUser $viewer) {
$handles = $this->getHandles();
if ($this->hasAttachedTransactions()) {
$transactions = $this->getTransactions();
} else {
$transactions = array();
}
$img_src = $this->getProfileImageURI();
$message_transaction = null;
foreach ($transactions as $transaction) {
if ($message_transaction) {
break;
}
switch ($transaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$message_transaction = $transaction;
break;
default:
break;
}
}
if ($message_transaction) {
$message_handle = $handles[$message_transaction->getAuthorPHID()];
$subtitle = sprintf(
'%s: %s',
$message_handle->getName(),
id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(60)
->truncateString(
$message_transaction->getComment()->getContent()));
} else {
// Kinda lame, but maybe add last message to cache?
$subtitle = pht('No recent messages');
}
$user_participation = $this->getParticipantIfExists($viewer->getPHID());
if ($user_participation) {
$user_seen_count = $user_participation->getSeenMessageCount();
} else {
$user_seen_count = 0;
}
$unread_count = $this->getMessageCount() - $user_seen_count;
- $title = $this->getDisplayTitle($viewer);
+ $title = $this->getTitle();
$topic = $this->getTopic();
return array(
'title' => $title,
'topic' => $topic,
'subtitle' => $subtitle,
'unread_count' => $unread_count,
'epoch' => $this->getDateModified(),
'image' => $img_src,
);
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
return PhabricatorPolicies::POLICY_NOONE;
}
public function hasAutomaticCapability($capability, PhabricatorUser $user) {
// this bad boy isn't even created yet so go nuts $user
if (!$this->getID()) {
return true;
}
switch ($capability) {
case PhabricatorPolicyCapability::CAN_EDIT:
return false;
}
$participants = $this->getParticipants();
return isset($participants[$user->getPHID()]);
}
public function describeAutomaticCapability($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return pht('Participants in a room can always view it.');
break;
}
}
public static function loadViewPolicyObjects(
PhabricatorUser $viewer,
array $conpherences) {
assert_instances_of($conpherences, __CLASS__);
$policies = array();
foreach ($conpherences as $room) {
$policies[$room->getViewPolicy()] = 1;
}
$policy_objects = array();
if ($policies) {
$policy_objects = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->withPHIDs(array_keys($policies))
->execute();
}
return $policy_objects;
}
public function getPolicyIconName(array $policy_objects) {
assert_instances_of($policy_objects, 'PhabricatorPolicy');
$icon = $policy_objects[$this->getViewPolicy()]->getIcon();
return $icon;
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new ConpherenceEditor();
}
public function getApplicationTransactionObject() {
return $this;
}
public function getApplicationTransactionTemplate() {
return new ConpherenceTransaction();
}
public function willRenderTimeline(
PhabricatorApplicationTransactionView $timeline,
AphrontRequest $request) {
return $timeline;
}
/* -( PhabricatorNgramInterface )------------------------------------------ */
public function newNgrams() {
return array(
id(new ConpherenceThreadTitleNgrams())
->setValue($this->getTitle()),
);
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$this->delete();
$participants = id(new ConpherenceParticipant())
->loadAllWhere('conpherencePHID = %s', $this->getPHID());
foreach ($participants as $participant) {
$participant->delete();
}
$this->saveTransaction();
}
}
diff --git a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php
index cd41d41148..6a91188c8f 100644
--- a/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php
+++ b/src/applications/search/menuitem/PhabricatorConpherenceProfileMenuItem.php
@@ -1,178 +1,177 @@
<?php
final class PhabricatorConpherenceProfileMenuItem
extends PhabricatorProfileMenuItem {
const MENUITEMKEY = 'conpherence';
const FIELD_CONPHERENCE = 'conpherence';
private $conpherence;
public function getMenuItemTypeIcon() {
return 'fa-comments';
}
public function getMenuItemTypeName() {
return pht('Conpherence');
}
public function canAddToObject($object) {
return true;
}
public function attachConpherence($conpherence) {
$this->conpherence = $conpherence;
return $this;
}
public function getConpherence() {
$conpherence = $this->conpherence;
if (!$conpherence) {
return null;
}
return $conpherence;
}
public function willBuildNavigationItems(array $items) {
$viewer = $this->getViewer();
$room_phids = array();
foreach ($items as $item) {
$room_phids[] = $item->getMenuItemProperty('conpherence');
}
$rooms = id(new ConpherenceThreadQuery())
->setViewer($viewer)
->withPHIDs($room_phids)
- ->needParticipantCache(true)
->needProfileImage(true)
->execute();
$rooms = mpull($rooms, null, 'getPHID');
foreach ($items as $item) {
$room_phid = $item->getMenuItemProperty('conpherence');
$room = idx($rooms, $room_phid, null);
$item->getMenuItem()->attachConpherence($room);
}
}
public function getDisplayName(
PhabricatorProfileMenuItemConfiguration $config) {
$room = $this->getConpherence($config);
if (!$room) {
return pht('(Restricted/Invalid Conpherence)');
}
$name = $this->getName($config);
if (strlen($name)) {
return $name;
}
return $room->getTitle();
}
public function buildEditEngineFields(
PhabricatorProfileMenuItemConfiguration $config) {
return array(
id(new PhabricatorDatasourceEditField())
->setKey(self::FIELD_CONPHERENCE)
->setLabel(pht('Conpherence Room'))
->setDatasource(new ConpherenceThreadDatasource())
->setIsRequired(true)
->setSingleValue($config->getMenuItemProperty('conpherence')),
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setValue($this->getName($config)),
);
}
private function getName(
PhabricatorProfileMenuItemConfiguration $config) {
return $config->getMenuItemProperty('name');
}
protected function newNavigationMenuItems(
PhabricatorProfileMenuItemConfiguration $config) {
$viewer = $this->getViewer();
$room = $this->getConpherence($config);
if (!$room) {
return array();
}
$participants = $room->getParticipants();
$viewer_phid = $viewer->getPHID();
$unread_count = null;
if (isset($participants[$viewer_phid])) {
$data = $room->getDisplayData($viewer);
$unread_count = $data['unread_count'];
}
$count = null;
if ($unread_count) {
$count = phutil_tag(
'span',
array(
'class' => 'phui-list-item-count',
),
$unread_count);
}
$item = $this->newItem()
->setHref('/'.$room->getMonogram())
->setName($this->getDisplayName($config))
->setIcon('fa-comments')
->appendChild($count);
return array(
$item,
);
}
public function validateTransactions(
PhabricatorProfileMenuItemConfiguration $config,
$field_key,
$value,
array $xactions) {
$viewer = $this->getViewer();
$errors = array();
if ($field_key == self::FIELD_CONPHERENCE) {
if ($this->isEmptyTransaction($value, $xactions)) {
$errors[] = $this->newRequiredError(
pht('You must choose a room.'),
$field_key);
}
foreach ($xactions as $xaction) {
$new = $xaction['new'];
if (!$new) {
continue;
}
if ($new === $value) {
continue;
}
$rooms = id(new ConpherenceThreadQuery())
->setViewer($viewer)
->withPHIDs(array($new))
->execute();
if (!$rooms) {
$errors[] = $this->newInvalidError(
pht(
'Room "%s" is not a valid room which you have '.
'permission to see.',
$new),
$xaction['xaction']);
}
}
}
return $errors;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Jun 9, 11:54 PM (13 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
140170
Default Alt Text
(109 KB)

Event Timeline