diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 03c341f090..b17044ead0 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -1,467 +1,466 @@ needFilePHIDs = $need_file_phids; return $this; } public function needParticipantCache($participant_cache) { $this->needParticipantCache = $participant_cache; return $this; } public function needParticipants($need) { $this->needParticipants = $need; return $this; } public function needWidgetData($need_widget_data) { $this->needWidgetData = $need_widget_data; return $this; } public function needCropPics($need) { $this->needCropPics = $need; return $this; } public function needOrigPics($need_widget_data) { $this->needOrigPics = $need_widget_data; 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 withIsRoom($bool) { $this->isRoom = $bool; 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; } protected function loadPage() { $table = new ConpherenceThread(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT conpherence_thread.* FROM %T conpherence_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->needWidgetData || $this->needParticipants) { $this->loadCoreHandles($conpherences, 'getParticipantPHIDs'); } if ($this->needTransactions) { $this->loadTransactionsAndHandles($conpherences); } if ($this->needFilePHIDs || $this->needWidgetData) { $this->loadFilePHIDs($conpherences); } if ($this->needWidgetData) { $this->loadWidgetData($conpherences); } if ($this->needOrigPics || $this->needCropPics) { $this->initImages($conpherences); } if ($this->needOrigPics) { $this->loadOrigPics($conpherences); } if ($this->needCropPics) { $this->loadCropPics($conpherences); } } return $conpherences; } protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { if ($this->participantPHIDs !== null || strlen($this->fulltext)) { return 'GROUP BY conpherence_thread.id'; } else { return $this->buildApplicationSearchGroupClause($conn_r); } } protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { $joins = array(); if ($this->participantPHIDs !== null) { $joins[] = qsprintf( $conn_r, 'JOIN %T p ON p.conpherencePHID = conpherence_thread.phid', id(new ConpherenceParticipant())->getTableName()); } $viewer = $this->getViewer(); if ($this->shouldJoinForViewer($viewer)) { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T v ON v.conpherencePHID = conpherence_thread.phid '. 'AND v.participantPHID = %s', id(new ConpherenceParticipant())->getTableName(), $viewer->getPHID()); } if (strlen($this->fulltext)) { $joins[] = qsprintf( $conn_r, 'JOIN %T idx ON idx.threadPHID = conpherence_thread.phid', id(new ConpherenceIndex())->getTableName()); } $joins[] = $this->buildApplicationSearchJoinClause($conn_r); return implode(' ', $joins); } private function shouldJoinForViewer(PhabricatorUser $viewer) { if ($viewer->isLoggedIn() && $this->ids === null && $this->phids === null) { return true; } return false; } protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids !== null) { $where[] = qsprintf( $conn_r, 'conpherence_thread.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn_r, 'conpherence_thread.phid IN (%Ls)', $this->phids); } if ($this->participantPHIDs !== null) { $where[] = qsprintf( $conn_r, 'p.participantPHID IN (%Ls)', $this->participantPHIDs); } if ($this->isRoom !== null) { $where[] = qsprintf( $conn_r, 'conpherence_thread.isRoom = %d', (int)$this->isRoom); } if (strlen($this->fulltext)) { $where[] = qsprintf( $conn_r, 'MATCH(idx.corpus) AGAINST (%s IN BOOLEAN MODE)', $this->fulltext); } $viewer = $this->getViewer(); if ($this->shouldJoinForViewer($viewer)) { $where[] = qsprintf( $conn_r, 'conpherence_thread.isRoom = 1 OR v.participantPHID IS NOT NULL'); } else if ($this->phids === null && $this->ids === null) { $where[] = qsprintf( $conn_r, 'conpherence_thread.isRoom = 1'); } return $this->formatWhereClause($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); - $handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($flat_phids) - ->execute(); + $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; } private function loadFilePHIDs(array $conpherences) { $edge_type = PhabricatorObjectHasFileEdgeType::EDGECONST; $file_edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array_keys($conpherences)) ->withEdgeTypes(array($edge_type)) ->execute(); foreach ($file_edges as $conpherence_phid => $data) { $conpherence = $conpherences[$conpherence_phid]; $conpherence->attachFilePHIDs(array_keys($data[$edge_type])); } return $this; } private function loadWidgetData(array $conpherences) { $participant_phids = array(); $file_phids = array(); foreach ($conpherences as $conpherence) { $participant_phids[] = array_keys($conpherence->getParticipants()); $file_phids[] = $conpherence->getFilePHIDs(); } $participant_phids = array_mergev($participant_phids); $file_phids = array_mergev($file_phids); $epochs = CalendarTimeUtil::getCalendarEventEpochs( $this->getViewer()); $start_epoch = $epochs['start_epoch']; $end_epoch = $epochs['end_epoch']; $statuses = id(new PhabricatorCalendarEventQuery()) ->setViewer($this->getViewer()) ->withInvitedPHIDs($participant_phids) ->withDateRange($start_epoch, $end_epoch) ->execute(); $statuses = mgroup($statuses, 'getUserPHID'); // attached files $files = array(); $file_author_phids = array(); $authors = array(); if ($file_phids) { $files = id(new PhabricatorFileQuery()) ->setViewer($this->getViewer()) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); $file_author_phids = mpull($files, 'getAuthorPHID', 'getPHID'); $authors = id(new PhabricatorHandleQuery()) ->setViewer($this->getViewer()) ->withPHIDs($file_author_phids) ->execute(); $authors = mpull($authors, null, 'getPHID'); } foreach ($conpherences as $phid => $conpherence) { $participant_phids = array_keys($conpherence->getParticipants()); $statuses = array_select_keys($statuses, $participant_phids); $statuses = array_mergev($statuses); $statuses = msort($statuses, 'getDateFrom'); $conpherence_files = array(); $files_authors = array(); foreach ($conpherence->getFilePHIDs() as $curr_phid) { $curr_file = idx($files, $curr_phid); if (!$curr_file) { // this file was deleted or user doesn't have permission to see it // this is generally weird continue; } $conpherence_files[$curr_phid] = $curr_file; // some files don't have authors so be careful $current_author = null; $current_author_phid = idx($file_author_phids, $curr_phid); if ($current_author_phid) { $current_author = $authors[$current_author_phid]; } $files_authors[$curr_phid] = $current_author; } $widget_data = array( 'statuses' => $statuses, 'files' => $conpherence_files, 'files_authors' => $files_authors, ); $conpherence->attachWidgetData($widget_data); } return $this; } private function loadOrigPics(array $conpherences) { return $this->loadPics( $conpherences, ConpherenceImageData::SIZE_ORIG); } private function loadCropPics(array $conpherences) { return $this->loadPics( $conpherences, ConpherenceImageData::SIZE_CROP); } private function initImages($conpherences) { foreach ($conpherences as $conpherence) { $conpherence->attachImages(array()); } } private function loadPics(array $conpherences, $size) { $conpherence_pic_phids = array(); foreach ($conpherences as $conpherence) { $phid = $conpherence->getImagePHID($size); if ($phid) { $conpherence_pic_phids[$conpherence->getPHID()] = $phid; } } if (!$conpherence_pic_phids) { return $this; } $files = id(new PhabricatorFileQuery()) ->setViewer($this->getViewer()) ->withPHIDs($conpherence_pic_phids) ->execute(); $files = mpull($files, null, 'getPHID'); foreach ($conpherence_pic_phids as $conpherence_phid => $pic_phid) { $conpherences[$conpherence_phid]->setImage($files[$pic_phid], $size); } return $this; } public function getQueryApplicationClass() { return 'PhabricatorConpherenceApplication'; } } diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php index ac6f713eeb..4c1e3ccd17 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php @@ -1,191 +1,189 @@ phids = $phids; return $this; } public function withObjectPHIDs(array $object_phids) { $this->objectPHIDs = $object_phids; return $this; } public function withAuthorPHIDs(array $author_phids) { $this->authorPHIDs = $author_phids; return $this; } public function withTransactionTypes(array $transaction_types) { $this->transactionTypes = $transaction_types; return $this; } public function needComments($need) { $this->needComments = $need; return $this; } public function needHandles($need) { $this->needHandles = $need; return $this; } protected function loadPage() { $table = $this->getTemplateApplicationTransaction(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T x %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $xactions = $table->loadAllFromArray($data); foreach ($xactions as $xaction) { $xaction->attachViewer($this->getViewer()); } if ($this->needComments) { $comment_phids = array_filter(mpull($xactions, 'getCommentPHID')); $comments = array(); if ($comment_phids) { $comments = id(new PhabricatorApplicationTransactionTemplatedCommentQuery()) ->setTemplate($table->getApplicationTransactionCommentObject()) ->setViewer($this->getViewer()) ->withPHIDs($comment_phids) ->execute(); $comments = mpull($comments, null, 'getPHID'); } foreach ($xactions as $xaction) { if ($xaction->getCommentPHID()) { $comment = idx($comments, $xaction->getCommentPHID()); if ($comment) { $xaction->attachComment($comment); } } } } else { foreach ($xactions as $xaction) { $xaction->setCommentNotLoaded(true); } } return $xactions; } protected function willFilterPage(array $xactions) { $object_phids = array_keys(mpull($xactions, null, 'getObjectPHID')); $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->setParentQuery($this) ->withPHIDs($object_phids) ->execute(); foreach ($xactions as $key => $xaction) { $object_phid = $xaction->getObjectPHID(); if (empty($objects[$object_phid])) { unset($xactions[$key]); continue; } $xaction->attachObject($objects[$object_phid]); } // NOTE: We have to do this after loading objects, because the objects // may help determine which handles are required (for example, in the case // of custom fields). if ($this->needHandles) { $phids = array(); foreach ($xactions as $xaction) { $phids[$xaction->getPHID()] = $xaction->getRequiredHandlePHIDs(); } $handles = array(); $merged = array_mergev($phids); if ($merged) { - $handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($merged) - ->execute(); + $handles = $this->getViewer()->loadHandles($merged); + $handles = iterator_to_array($handles); } foreach ($xactions as $xaction) { $xaction->setHandles( array_select_keys( $handles, $phids[$xaction->getPHID()])); } } return $xactions; } protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->objectPHIDs) { $where[] = qsprintf( $conn_r, 'objectPHID IN (%Ls)', $this->objectPHIDs); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->transactionTypes) { $where[] = qsprintf( $conn_r, 'transactionType IN (%Ls)', $this->transactionTypes); } foreach ($this->buildMoreWhereClauses($conn_r) as $clause) { $where[] = $clause; } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } public function getQueryApplicationClass() { // TODO: Sort this out? return null; } } diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php index 0b4471d811..28377c19d5 100644 --- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php @@ -1,377 +1,376 @@ getObjectNamePrefix(); return preg_match('/^\w/', $prefix); } protected function getObjectIDPattern() { return '[1-9]\d*'; } protected function shouldMarkupObject(array $params) { return true; } protected function loadHandles(array $objects) { $phids = mpull($objects, 'getPHID'); - $handles = id(new PhabricatorHandleQuery($phids)) - ->withPHIDs($phids) - ->setViewer($this->getEngine()->getConfig('viewer')) - ->execute(); + $viewer = $this->getEngine()->getConfig('viewer'); + $handles = $viewer->loadHandles($phids); + $handles = iterator_to_array($handles); $result = array(); foreach ($objects as $id => $object) { $result[$id] = $handles[$object->getPHID()]; } return $result; } protected function getObjectHref( $object, PhabricatorObjectHandle $handle, $id) { return $handle->getURI(); } protected function renderObjectRefForAnyMedia ( $object, PhabricatorObjectHandle $handle, $anchor, $id) { $href = $this->getObjectHref($object, $handle, $id); $text = $this->getObjectNamePrefix().$id; if ($anchor) { $href = $href.'#'.$anchor; $text = $text.'#'.$anchor; } if ($this->getEngine()->isTextMode()) { return PhabricatorEnv::getProductionURI($href); } else if ($this->getEngine()->isHTMLMailMode()) { $href = PhabricatorEnv::getProductionURI($href); return $this->renderObjectTagForMail($text, $href, $handle); } return $this->renderObjectRef($object, $handle, $anchor, $id); } protected function renderObjectRef( $object, PhabricatorObjectHandle $handle, $anchor, $id) { $href = $this->getObjectHref($object, $handle, $id); $text = $this->getObjectNamePrefix().$id; $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; if ($anchor) { $href = $href.'#'.$anchor; $text = $text.'#'.$anchor; } $attr = array( 'phid' => $handle->getPHID(), 'closed' => ($handle->getStatus() == $status_closed), ); return $this->renderHovertag($text, $href, $attr); } protected function renderObjectEmbedForAnyMedia( $object, PhabricatorObjectHandle $handle, $options) { $name = $handle->getFullName(); $href = $handle->getURI(); if ($this->getEngine()->isTextMode()) { return $name.' <'.PhabricatorEnv::getProductionURI($href).'>'; } else if ($this->getEngine()->isHTMLMailMode()) { $href = PhabricatorEnv::getProductionURI($href); return $this->renderObjectTagForMail($name, $href, $handle); } return $this->renderObjectEmbed($object, $handle, $options); } protected function renderObjectEmbed( $object, PhabricatorObjectHandle $handle, $options) { $name = $handle->getFullName(); $href = $handle->getURI(); $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; $attr = array( 'phid' => $handle->getPHID(), 'closed' => ($handle->getStatus() == $status_closed), ); return $this->renderHovertag($name, $href, $attr); } protected function renderObjectTagForMail( $text, $href, PhabricatorObjectHandle $handle) { $status_closed = PhabricatorObjectHandleStatus::STATUS_CLOSED; $strikethrough = $handle->getStatus() == $status_closed ? 'text-decoration: line-through;' : 'text-decoration: none;'; return phutil_tag( 'a', array( 'href' => $href, 'style' => 'background-color: #e7e7e7; border-color: #e7e7e7; border-radius: 3px; padding: 0 4px; font-weight: bold; color: black;' .$strikethrough, ), $text); } protected function renderHovertag($name, $href, array $attr = array()) { return id(new PHUITagView()) ->setName($name) ->setHref($href) ->setType(PHUITagView::TYPE_OBJECT) ->setPHID(idx($attr, 'phid')) ->setClosed(idx($attr, 'closed')) ->render(); } public function apply($text) { $text = preg_replace_callback( $this->getObjectEmbedPattern(), array($this, 'markupObjectEmbed'), $text); $text = preg_replace_callback( $this->getObjectReferencePattern(), array($this, 'markupObjectReference'), $text); return $text; } private function getObjectEmbedPattern() { $prefix = $this->getObjectNamePrefix(); $prefix = preg_quote($prefix); $id = $this->getObjectIDPattern(); return '(\B{'.$prefix.'('.$id.')([,\s](?:[^}\\\\]|\\\\.)*)?}\B)u'; } private function getObjectReferencePattern() { $prefix = $this->getObjectNamePrefix(); $prefix = preg_quote($prefix); $id = $this->getObjectIDPattern(); // If the prefix starts with a word character (like "D"), we want to // require a word boundary so that we don't match "XD1" as "D1". If the // prefix does not start with a word character, we want to require no word // boundary for the same reasons. Test if the prefix starts with a word // character. if ($this->getObjectNamePrefixBeginsWithWordCharacter()) { $boundary = '\\b'; } else { $boundary = '\\B'; } // The "(?getObjectEmbedPattern(), $text, $embed_matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); $ref_matches = null; preg_match_all( $this->getObjectReferencePattern(), $text, $ref_matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); $results = array(); $sets = array( 'embed' => $embed_matches, 'ref' => $ref_matches, ); foreach ($sets as $type => $matches) { $formatted = array(); foreach ($matches as $match) { $format = array( 'offset' => $match[1][1], 'id' => $match[1][0], ); if (isset($match[2][0])) { $format['tail'] = $match[2][0]; } $formatted[] = $format; } $results[$type] = $formatted; } return $results; } public function markupObjectEmbed(array $matches) { if (!$this->isFlatText($matches[0])) { return $matches[0]; } return $this->markupObject(array( 'type' => 'embed', 'id' => $matches[1], 'options' => idx($matches, 2), 'original' => $matches[0], )); } public function markupObjectReference(array $matches) { if (!$this->isFlatText($matches[0])) { return $matches[0]; } return $this->markupObject(array( 'type' => 'ref', 'id' => $matches[1], 'anchor' => idx($matches, 2), 'original' => $matches[0], )); } private function markupObject(array $params) { if (!$this->shouldMarkupObject($params)) { return $params['original']; } $regex = trim( PhabricatorEnv::getEnvConfig('remarkup.ignored-object-names')); if ($regex && preg_match($regex, $params['original'])) { return $params['original']; } $engine = $this->getEngine(); $token = $engine->storeText('x'); $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix(); $metadata = $engine->getTextMetadata($metadata_key, array()); $metadata[] = array( 'token' => $token, ) + $params; $engine->setTextMetadata($metadata_key, $metadata); return $token; } public function didMarkupText() { $engine = $this->getEngine(); $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix(); $metadata = $engine->getTextMetadata($metadata_key, array()); if (!$metadata) { return; } $ids = ipull($metadata, 'id'); $objects = $this->loadObjects($ids); // For objects that are invalid or which the user can't see, just render // the original text. // TODO: We should probably distinguish between these cases and render a // "you can't see this" state for nonvisible objects. foreach ($metadata as $key => $spec) { if (empty($objects[$spec['id']])) { $engine->overwriteStoredText( $spec['token'], $spec['original']); unset($metadata[$key]); } } $phids = $engine->getTextMetadata(self::KEY_MENTIONED_OBJECTS, array()); foreach ($objects as $object) { $phids[$object->getPHID()] = $object->getPHID(); } $engine->setTextMetadata(self::KEY_MENTIONED_OBJECTS, $phids); $handles = $this->loadHandles($objects); foreach ($metadata as $key => $spec) { $handle = $handles[$spec['id']]; $object = $objects[$spec['id']]; switch ($spec['type']) { case 'ref': $view = $this->renderObjectRefForAnyMedia( $object, $handle, $spec['anchor'], $spec['id']); break; case 'embed': $spec['options'] = $this->assertFlatText($spec['options']); $view = $this->renderObjectEmbedForAnyMedia( $object, $handle, $spec['options']); break; } $engine->overwriteStoredText($spec['token'], $view); } $engine->setTextMetadata($metadata_key, array()); } }