diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 9c6682a8a7..99fd18878e 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -1,338 +1,344 @@ 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->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'; + return qsprintf($conn_r, '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(); + $policies = array( + $members_policy, + PhabricatorPolicies::POLICY_USER, + PhabricatorPolicies::POLICY_ADMIN, + PhabricatorPolicies::POLICY_NOONE, + ); if ($viewer->isLoggedIn()) { $where[] = qsprintf( $conn, - 'thread.viewPolicy != %s OR vp.participantPHID = %s', - $members_policy, + 'thread.viewPolicy NOT IN (%Ls) OR vp.participantPHID = %s', + $policies, $viewer->getPHID()); } else { $where[] = qsprintf( $conn, - 'thread.viewPolicy != %s', - $members_policy); + 'thread.viewPolicy NOT IN (%Ls)', + $policies); } } 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) { // NOTE: This is older code which has been modernized to the minimum // standard required by T13266. It probably isn't the best available // approach to the problems it solves. $limit = $this->getTransactionLimit(); if ($limit) { // fetch an extra for "show older" scenarios $limit = $limit + 1; } else { $limit = 0xFFFF; } $pager = id(new AphrontCursorPagerView()) ->setPageSize($limit); // We have to flip these for the underlying query class. The semantics of // paging are tricky business. if ($this->afterTransactionID) { $pager->setBeforeID($this->afterTransactionID); } else if ($this->beforeTransactionID) { $pager->setAfterID($this->beforeTransactionID); } $transactions = id(new ConpherenceTransactionQuery()) ->setViewer($this->getViewer()) ->withObjectPHIDs(array_keys($conpherences)) ->needHandles(true) ->executeWithCursorPager($pager); $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/view/ConpherenceLayoutView.php b/src/applications/conpherence/view/ConpherenceLayoutView.php index 7dbc00a325..3382a9ba87 100644 --- a/src/applications/conpherence/view/ConpherenceLayoutView.php +++ b/src/applications/conpherence/view/ConpherenceLayoutView.php @@ -1,269 +1,269 @@ messages = $messages; return $this; } public function setReplyForm($reply_form) { $this->replyForm = $reply_form; return $this; } public function setHeader($header) { $this->header = $header; return $this; } public function setSearch($search) { $this->search = $search; return $this; } public function setRole($role) { $this->role = $role; return $this; } public function getThreadView() { return $this->threadView; } public function setBaseURI($base_uri) { $this->baseURI = $base_uri; return $this; } public function setThread(ConpherenceThread $thread) { $this->thread = $thread; return $this; } public function setThreadView(ConpherenceThreadListView $thead_view) { $this->threadView = $thead_view; return $this; } public function setTheme($theme) { $this->theme = $theme; return $this; } public function setLatestTransactionID($id) { $this->latestTransactionID = $id; return $this; } protected function getTagAttributes() { $classes = array(); $classes[] = 'conpherence-layout'; $classes[] = 'hide-widgets'; $classes[] = 'conpherence-role-'.$this->role; $classes[] = ConpherenceRoomSettings::getThemeClass($this->theme); return array( 'id' => 'conpherence-main-layout', 'sigil' => 'conpherence-layout', 'class' => implode(' ', $classes), ); } protected function getTagContent() { require_celerity_resource('conpherence-menu-css'); require_celerity_resource('conpherence-message-pane-css'); require_celerity_resource('conpherence-participant-pane-css'); $selected_id = null; $selected_thread_id = null; $selected_thread_phid = null; $can_edit_selected = null; $nux = null; if ($this->thread) { $selected_id = $this->thread->getPHID().'-nav-item'; $selected_thread_id = $this->thread->getID(); $selected_thread_phid = $this->thread->getPHID(); $can_edit_selected = PhabricatorPolicyFilter::hasCapability( $this->getUser(), $this->thread, PhabricatorPolicyCapability::CAN_EDIT); } else { $nux = $this->buildNUXView(); } $this->initBehavior('conpherence-menu', array( 'baseURI' => $this->baseURI, 'layoutID' => 'conpherence-main-layout', 'selectedID' => $selected_id, 'selectedThreadID' => $selected_thread_id, 'selectedThreadPHID' => $selected_thread_phid, 'canEditSelectedThread' => $can_edit_selected, 'latestTransactionID' => $this->latestTransactionID, 'role' => $this->role, 'theme' => ConpherenceRoomSettings::getThemeClass($this->theme), 'hasThreadList' => (bool)$this->threadView, 'hasThread' => (bool)$this->messages, 'hasWidgets' => false, )); $this->initBehavior('conpherence-participant-pane'); return array( javelin_tag( 'div', array( 'id' => 'conpherence-menu-pane', 'class' => 'conpherence-menu-pane phabricator-side-menu', 'sigil' => 'conpherence-menu-pane', ), $this->threadView), javelin_tag( 'div', array( 'class' => 'conpherence-content-pane', ), array( phutil_tag( 'div', array( 'class' => 'conpherence-loading-mask', ), ''), javelin_tag( 'div', array( 'class' => 'conpherence-header-pane', 'id' => 'conpherence-header-pane', 'sigil' => 'conpherence-header-pane', ), nonempty($this->header, '')), javelin_tag( 'div', array( 'class' => 'conpherence-no-threads', 'sigil' => 'conpherence-no-threads', 'style' => 'display: none;', ), $nux), javelin_tag( 'div', array( 'class' => 'conpherence-participant-pane', 'id' => 'conpherence-participant-pane', 'sigil' => 'conpherence-participant-pane', ), array( phutil_tag( 'div', array( 'class' => 'widgets-loading-mask', ), ''), javelin_tag( 'div', array( 'sigil' => 'conpherence-widgets-holder', ), ''), )), javelin_tag( 'div', array( 'class' => 'conpherence-message-pane', 'id' => 'conpherence-message-pane', 'sigil' => 'conpherence-message-pane', ), array( javelin_tag( 'div', array( 'class' => 'conpherence-messages', 'id' => 'conpherence-messages', 'sigil' => 'conpherence-messages', ), nonempty($this->messages, '')), javelin_tag( 'div', array( 'class' => 'conpherence-search-main', 'id' => 'conpherence-search-main', 'sigil' => 'conpherence-search-main', ), nonempty($this->search, '')), phutil_tag( 'div', array( 'class' => 'messages-loading-mask', ), ''), javelin_tag( 'div', array( 'id' => 'conpherence-form', 'sigil' => 'conpherence-form', ), nonempty($this->replyForm, '')), )), )), ); } private function buildNUXView() { $viewer = $this->getViewer(); - $engine = new ConpherenceThreadSearchEngine(); - $engine->setViewer($viewer); + $engine = id(new ConpherenceThreadSearchEngine()) + ->setViewer($viewer); $saved = $engine->buildSavedQueryFromBuiltin('all'); $query = $engine->buildQueryFromSavedQuery($saved); - $pager = $engine->newPagerForSavedQuery($saved); - $pager->setPageSize(10); + $pager = $engine->newPagerForSavedQuery($saved) + ->setPageSize(10); $results = $engine->executeQuery($query, $pager); $view = $engine->renderResults($results, $saved); $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('New Room')) ->setHref('/conpherence/new/') ->setWorkflow(true) ->setColor(PHUIButtonView::GREEN); if ($results) { $create_button->setIcon('fa-comments'); $header = id(new PHUIHeaderView()) ->setHeader(pht('Joinable Rooms')) ->addActionLink($create_button); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->setObjectList($view->getContent()); return $box; } else { $view = id(new PHUIBigInfoView()) ->setIcon('fa-comments') ->setTitle(pht('Welcome to Conpherence')) ->setDescription( pht('Conpherence lets you create public or private rooms to '. 'communicate with others.')) ->addAction($create_button); return $view; } } }