diff --git a/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php b/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php index 7362bddf7d..46b1c51211 100644 --- a/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php +++ b/src/applications/auth/query/PhabricatorAuthProviderConfigQuery.php @@ -1,99 +1,103 @@ phids = $phids; return $this; } public function withIDs(array $ids) { $this->ids = $ids; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withProviderClasses(array $classes) { $this->providerClasses = $classes; return $this; } public static function getStatusOptions() { return array( self::STATUS_ALL => pht('All Providers'), self::STATUS_ENABLED => pht('Enabled Providers'), ); } protected function loadPage() { $table = new PhabricatorAuthProviderConfig(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->providerClasses) { $where[] = qsprintf( $conn_r, 'providerClass IN (%Ls)', $this->providerClasses); } $status = $this->status; switch ($status) { case self::STATUS_ALL: break; case self::STATUS_ENABLED: $where[] = qsprintf( $conn_r, 'isEnabled = 1'); break; default: throw new Exception("Unknown status '{$status}'!"); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationAuth'; + } + } diff --git a/src/applications/auth/query/PhabricatorExternalAccountQuery.php b/src/applications/auth/query/PhabricatorExternalAccountQuery.php index b8cb53cc2c..37ec836065 100644 --- a/src/applications/auth/query/PhabricatorExternalAccountQuery.php +++ b/src/applications/auth/query/PhabricatorExternalAccountQuery.php @@ -1,166 +1,170 @@ userPHIDs = $user_phids; return $this; } public function withAccountIDs(array $account_ids) { $this->accountIDs = $account_ids; return $this; } public function withAccountDomains(array $account_domains) { $this->accountDomains = $account_domains; return $this; } public function withAccountTypes(array $account_types) { $this->accountTypes = $account_types; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withIDs($ids) { $this->ids = $ids; return $this; } public function withAccountSecrets(array $secrets) { $this->accountSecrets = $secrets; return $this; } public function needImages($need) { $this->needImages = $need; return $this; } public function loadPage() { $table = new PhabricatorExternalAccount(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $accounts) { if ($this->needImages) { $file_phids = mpull($accounts, 'getProfileImagePHID'); $file_phids = array_filter($file_phids); if ($file_phids) { // NOTE: We use the omnipotent viewer here because these files are // usually created during registration and can't be associated with // the correct policies, since the relevant user account does not exist // yet. In effect, if you can see an ExternalAccount, you can see its // profile image. $files = id(new PhabricatorFileQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); } else { $files = array(); } $default_file = null; foreach ($accounts as $account) { $image_phid = $account->getProfileImagePHID(); if ($image_phid && isset($files[$image_phid])) { $account->attachProfileImageFile($files[$image_phid]); } else { if ($default_file === null) { $default_file = PhabricatorFile::loadBuiltin( $this->getViewer(), 'profile.png'); } $account->attachProfileImageFile($default_file); } } } return $accounts; } protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->accountTypes) { $where[] = qsprintf( $conn_r, 'accountType IN (%Ls)', $this->accountTypes); } if ($this->accountDomains) { $where[] = qsprintf( $conn_r, 'accountDomain IN (%Ls)', $this->accountDomains); } if ($this->accountIDs) { $where[] = qsprintf( $conn_r, 'accountID IN (%Ls)', $this->accountIDs); } if ($this->userPHIDs) { $where[] = qsprintf( $conn_r, 'userPHID IN (%Ls)', $this->userPHIDs); } if ($this->accountSecrets) { $where[] = qsprintf( $conn_r, 'accountSecret IN (%Ls)', $this->accountSecrets); } return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPeople'; + } + } diff --git a/src/applications/chatlog/PhabricatorChatLogChannelQuery.php b/src/applications/chatlog/PhabricatorChatLogChannelQuery.php index 98d23bd66d..cba9976c79 100644 --- a/src/applications/chatlog/PhabricatorChatLogChannelQuery.php +++ b/src/applications/chatlog/PhabricatorChatLogChannelQuery.php @@ -1,58 +1,63 @@ channels = $channels; return $this; } public function withIDs(array $channel_ids) { $this->channelIDs = $channel_ids; return $this; } protected function loadPage() { $table = new PhabricatorChatLogChannel(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T c %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $logs = $table->loadAllFromArray($data); return $logs; } private function buildWhereClause($conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->channelIDs) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->channelIDs); } if ($this->channels) { $where[] = qsprintf( $conn_r, 'channelName IN (%Ls)', $this->channels); } return $this->formatWhereClause($where); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationChatlog'; + } + } diff --git a/src/applications/chatlog/PhabricatorChatLogQuery.php b/src/applications/chatlog/PhabricatorChatLogQuery.php index 1c5a5ef43c..599a1d5c6d 100644 --- a/src/applications/chatlog/PhabricatorChatLogQuery.php +++ b/src/applications/chatlog/PhabricatorChatLogQuery.php @@ -1,57 +1,62 @@ channelIDs = $channel_ids; return $this; } public function withMaximumEpoch($epoch) { $this->maximumEpoch = $epoch; return $this; } protected function loadPage() { $table = new PhabricatorChatLogEvent(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T e %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $logs = $table->loadAllFromArray($data); return $logs; } private function buildWhereClause($conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->maximumEpoch) { $where[] = qsprintf( $conn_r, 'epoch <= %d', $this->maximumEpoch); } if ($this->channelIDs) { $where[] = qsprintf( $conn_r, 'channelID IN (%Ld)', $this->channelIDs); } return $this->formatWhereClause($where); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationChatlog'; + } + } diff --git a/src/applications/conduit/query/PhabricatorConduitLogQuery.php b/src/applications/conduit/query/PhabricatorConduitLogQuery.php index 092f9d59bc..539d84785f 100644 --- a/src/applications/conduit/query/PhabricatorConduitLogQuery.php +++ b/src/applications/conduit/query/PhabricatorConduitLogQuery.php @@ -1,43 +1,47 @@ methods = $methods; return $this; } public function loadPage() { $table = new PhabricatorConduitMethodCallLog(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data);; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->methods) { $where[] = qsprintf( $conn_r, 'method IN (%Ls)', $this->methods); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationConduit'; + } + } diff --git a/src/applications/conduit/query/PhabricatorConduitMethodQuery.php b/src/applications/conduit/query/PhabricatorConduitMethodQuery.php index 02236d85b3..40c55a90c9 100644 --- a/src/applications/conduit/query/PhabricatorConduitMethodQuery.php +++ b/src/applications/conduit/query/PhabricatorConduitMethodQuery.php @@ -1,124 +1,128 @@ methods = $methods; return $this; } public function withNameContains($name_contains) { $this->nameContains = $name_contains; return $this; } public function withApplicationNames(array $application_names) { $this->applicationNames = $application_names; return $this; } public function withIsStable($is_stable) { $this->isStable = $is_stable; return $this; } public function withIsUnstable($is_unstable) { $this->isUnstable = $is_unstable; return $this; } public function withIsDeprecated($is_deprecated) { $this->isDeprecated = $is_deprecated; return $this; } public function loadPage() { $methods = $this->getAllMethods(); $methods = $this->filterMethods($methods); return $methods; } private function getAllMethods() { static $methods; if ($methods === null) { $methods = id(new PhutilSymbolLoader()) ->setAncestorClass('ConduitAPIMethod') ->loadObjects(); $methods = msort($methods, 'getSortOrder'); } return $methods; } private function filterMethods(array $methods) { foreach ($methods as $key => $method) { $application = $method->getApplication(); if (!$application) { continue; } if (!$application->isInstalled()) { unset($methods[$key]); } } $status = array( ConduitAPIMethod::METHOD_STATUS_STABLE => $this->isStable, ConduitAPIMethod::METHOD_STATUS_DEPRECATED => $this->isDeprecated, ConduitAPIMethod::METHOD_STATUS_UNSTABLE => $this->isUnstable, ); // Only apply status filters if any of them are set. if (array_filter($status)) { foreach ($methods as $key => $method) { $keep = idx($status, $method->getMethodStatus()); if (!$keep) { unset($methods[$key]); } } } if ($this->applicationNames) { $map = array_fuse($this->applicationNames); foreach ($methods as $key => $method) { $needle = $method->getApplicationName(); $needle = phutil_utf8_strtolower($needle); if (empty($map[$needle])) { unset($methods[$key]); } } } if ($this->nameContains) { $needle = phutil_utf8_strtolower($this->nameContains); foreach ($methods as $key => $method) { $haystack = $method->getAPIMethodName(); $haystack = phutil_utf8_strtolower($haystack); if (strpos($haystack, $needle) === false) { unset($methods[$key]); } } } if ($this->methods) { $map = array_fuse($this->methods); foreach ($methods as $key => $method) { $needle = $method->getAPIMethodName(); if (empty($map[$needle])) { unset($methods[$key]); } } } return $methods; } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationConduit'; + } + } diff --git a/src/applications/config/phid/PhabricatorConfigPHIDTypeConfig.php b/src/applications/config/phid/PhabricatorConfigPHIDTypeConfig.php index ae297edbef..204ab5b9a8 100644 --- a/src/applications/config/phid/PhabricatorConfigPHIDTypeConfig.php +++ b/src/applications/config/phid/PhabricatorConfigPHIDTypeConfig.php @@ -1,49 +1,46 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $entry = $objects[$phid]; $key = $entry->getConfigKey(); $handle->setName($key); $handle->setURI("/config/edit/{$key}/"); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/config/query/PhabricatorConfigEntryQuery.php b/src/applications/config/query/PhabricatorConfigEntryQuery.php index 1f4026b58c..b23174bcd3 100644 --- a/src/applications/config/query/PhabricatorConfigEntryQuery.php +++ b/src/applications/config/query/PhabricatorConfigEntryQuery.php @@ -1,56 +1,60 @@ ids = $ids; return $this; } public function withPHIDs($phids) { $this->phids = $phids; return $this; } public function loadPage() { $table = new PhabricatorConfigEntry(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationConfig'; + } + } diff --git a/src/applications/conpherence/phid/PhabricatorConpherencePHIDTypeThread.php b/src/applications/conpherence/phid/PhabricatorConpherencePHIDTypeThread.php index dba81f2642..fd94da83a9 100644 --- a/src/applications/conpherence/phid/PhabricatorConpherencePHIDTypeThread.php +++ b/src/applications/conpherence/phid/PhabricatorConpherencePHIDTypeThread.php @@ -1,50 +1,47 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $thread = $objects[$phid]; $name = $thread->getTitle(); if (!strlen($name)) { $name = pht('[No Title]'); } $handle->setName($name); $handle->setFullName($name); $handle->setURI('/conpherence/'.$thread->getID().'/'); } } } diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 0017b3ed2b..f36d9d8fd8 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -1,285 +1,289 @@ needFilePHIDs = $need_file_phids; return $this; } public function needParticipantCache($participant_cache) { $this->needParticipantCache = $participant_cache; return $this; } public function needWidgetData($need_widget_data) { $this->needWidgetData = $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 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; } 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', $table->getTableName(), $this->buildWhereClause($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'); } else if ($this->needWidgetData) { $this->loadCoreHandles($conpherences, 'getParticipantPHIDs'); } if ($this->needTransactions) { $this->loadTransactionsAndHandles($conpherences); } if ($this->needFilePHIDs || $this->needWidgetData) { $this->loadFilePHIDs($conpherences); } if ($this->needWidgetData) { $this->loadWidgetData($conpherences); } } return $conpherences; } protected function buildWhereClause($conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } 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 ($map as $conpherence_phid => $conpherence_participants) { $current_conpherence = $conpherences[$conpherence_phid]; $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(); foreach ($handle_phids as $conpherence_phid => $phids) { $conpherence = $conpherences[$conpherence_phid]; $conpherence->attachHandles(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 = $transactions[$phid]; $handles = array(); foreach ($current_transactions as $transaction) { $handles += $transaction->getHandles(); } $conpherence->attachHandles($conpherence->getHandles() + $handles); $conpherence->attachTransactions($transactions[$phid]); } return $this; } private function loadFilePHIDs(array $conpherences) { $edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_FILE; $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 = ConpherenceTimeUtil::getCalendarEventEpochs( $this->getViewer()); $start_epoch = $epochs['start_epoch']; $end_epoch = $epochs['end_epoch']; $statuses = id(new PhabricatorUserStatus()) ->loadAllWhere( 'userPHID in (%Ls) AND dateTo >= %d AND dateFrom <= %d', $participant_phids, $start_epoch, $end_epoch); $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; } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationConpherence'; + } + } diff --git a/src/applications/countdown/phid/PhabricatorCountdownPHIDTypeCountdown.php b/src/applications/countdown/phid/PhabricatorCountdownPHIDTypeCountdown.php index c816a1054d..ef60213d36 100644 --- a/src/applications/countdown/phid/PhabricatorCountdownPHIDTypeCountdown.php +++ b/src/applications/countdown/phid/PhabricatorCountdownPHIDTypeCountdown.php @@ -1,76 +1,73 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $countdown = $objects[$phid]; $name = $countdown->getTitle(); $id = $countdown->getID(); $handle->setName("C{$id}"); $handle->setFullName("C{$id}: {$name}"); $handle->setURI("/countdown/{$id}/"); } } public function canLoadNamedObject($name) { return preg_match('/^C\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new PhabricatorCountdownQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/countdown/query/PhabricatorCountdownQuery.php b/src/applications/countdown/query/PhabricatorCountdownQuery.php index dc3de9c760..6d82fe268b 100644 --- a/src/applications/countdown/query/PhabricatorCountdownQuery.php +++ b/src/applications/countdown/query/PhabricatorCountdownQuery.php @@ -1,88 +1,92 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $author_phids) { $this->authorPHIDs = $author_phids; return $this; } public function withUpcoming($upcoming) { $this->upcoming = true; return $this; } protected function loadPage() { $table = new PhabricatorCountdown(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $countdowns = $table->loadAllFromArray($data); return $countdowns; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'authorPHID in (%Ls)', $this->authorPHIDs); } if ($this->upcoming) { $where[] = qsprintf( $conn_r, 'epoch >= %d', time()); } return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationCountdown'; + } + } diff --git a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php index 448c6f8927..656edd7bdc 100644 --- a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php +++ b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php @@ -1,145 +1,149 @@ ids = $ids; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withDaemonClasses(array $classes) { $this->daemonClasses = $classes; return $this; } public function loadPage() { $table = new PhabricatorDaemonLog(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $daemons) { $unknown_delay = PhabricatorDaemonLogQuery::getTimeUntilUnknown(); $dead_delay = PhabricatorDaemonLogQuery::getTimeUntilDead(); $status_running = PhabricatorDaemonLog::STATUS_RUNNING; $status_unknown = PhabricatorDaemonLog::STATUS_UNKNOWN; $status_wait = PhabricatorDaemonLog::STATUS_WAIT; $status_exited = PhabricatorDaemonLog::STATUS_EXITED; $status_dead = PhabricatorDaemonLog::STATUS_DEAD; $filter = array_fuse($this->getStatusConstants()); foreach ($daemons as $key => $daemon) { $status = $daemon->getStatus(); $seen = $daemon->getDateModified(); $is_running = ($status == $status_running) || ($status == $status_wait); // If we haven't seen the daemon recently, downgrade its status to // unknown. $unknown_time = ($seen + $unknown_delay); if ($is_running && ($unknown_time < time())) { $status = $status_unknown; } // If the daemon hasn't been seen in quite a while, assume it is dead. $dead_time = ($seen + $dead_delay); if (($status == $status_unknown) && ($dead_time < time())) { $status = $status_dead; } // If we changed the daemon's status, update it. if ($status != $daemon->getStatus()) { $guard = AphrontWriteGuard::beginScopedUnguardedWrites(); $daemon->setStatus($status)->save(); unset($guard); } // If the daemon no longer matches the filter, get rid of it. if ($filter) { if (empty($filter[$daemon->getStatus()])) { unset($daemons[$key]); } } } return $daemons; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->getStatusConstants()) { $where[] = qsprintf( $conn_r, 'status IN (%Ls)', $this->getStatusConstants()); } if ($this->daemonClasses) { $where[] = qsprintf( $conn_r, 'daemon IN (%Ls)', $this->daemonClasses); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function getStatusConstants() { $status = $this->status; switch ($status) { case self::STATUS_ALL: return array(); case self::STATUS_ALIVE: return array( PhabricatorDaemonLog::STATUS_UNKNOWN, PhabricatorDaemonLog::STATUS_RUNNING, PhabricatorDaemonLog::STATUS_WAIT, ); default: throw new Exception("Unknown status '{$status}'!"); } } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationDaemons'; + } + } diff --git a/src/applications/differential/phid/DifferentialPHIDTypeRevision.php b/src/applications/differential/phid/DifferentialPHIDTypeRevision.php index 414afc7d0d..f13bd6770f 100644 --- a/src/applications/differential/phid/DifferentialPHIDTypeRevision.php +++ b/src/applications/differential/phid/DifferentialPHIDTypeRevision.php @@ -1,86 +1,83 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { static $closed_statuses = array( ArcanistDifferentialRevisionStatus::CLOSED => true, ArcanistDifferentialRevisionStatus::ABANDONED => true, ); foreach ($handles as $phid => $handle) { $revision = $objects[$phid]; $title = $revision->getTitle(); $id = $revision->getID(); $status = $revision->getStatus(); $handle->setName("D{$id}"); $handle->setURI("/D{$id}"); $handle->setFullName("D{$id}: {$title}"); if (isset($closed_statuses[$status])) { $handle->setStatus(PhabricatorObjectHandleStatus::STATUS_CLOSED); } } } public function canLoadNamedObject($name) { return preg_match('/^D\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new DifferentialRevisionQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/differential/query/DifferentialDiffQuery.php b/src/applications/differential/query/DifferentialDiffQuery.php index a608a5421e..ec3265caa2 100644 --- a/src/applications/differential/query/DifferentialDiffQuery.php +++ b/src/applications/differential/query/DifferentialDiffQuery.php @@ -1,140 +1,144 @@ ids = $ids; return $this; } public function withRevisionIDs(array $revision_ids) { $this->revisionIDs = $revision_ids; return $this; } public function needChangesets($bool) { $this->needChangesets = $bool; return $this; } public function needArcanistProjects($bool) { $this->needArcanistProjects = $bool; return $this; } public function loadPage() { $table = new DifferentialDiff(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $diffs) { $revision_ids = array_filter(mpull($diffs, 'getRevisionID')); $revisions = array(); if ($revision_ids) { $revisions = id(new DifferentialRevisionQuery()) ->setViewer($this->getViewer()) ->withIDs($revision_ids) ->execute(); } foreach ($diffs as $key => $diff) { if (!$diff->getRevisionID()) { $diff->attachRevision(null); continue; } $revision = idx($revisions, $diff->getRevisionID()); if ($revision) { $diff->attachRevision($revision); continue; } unset($diffs[$key]); } if ($this->needChangesets) { $this->loadChangesets($diffs); } if ($this->needArcanistProjects) { $this->loadArcanistProjects($diffs); } return $diffs; } private function loadChangesets(array $diffs) { foreach ($diffs as $diff) { $diff->attachChangesets( $diff->loadRelatives(new DifferentialChangeset(), 'diffID')); foreach ($diff->getChangesets() as $changeset) { $changeset->attachHunks( $changeset->loadRelatives(new DifferentialHunk(), 'changesetID')); } } return $diffs; } private function loadArcanistProjects(array $diffs) { $phids = array_filter(mpull($diffs, 'getArcanistProjectPHID')); $projects = array(); $project_map = array(); if ($phids) { $projects = id(new PhabricatorRepositoryArcanistProject()) ->loadAllWhere( 'phid IN (%Ls)', $phids); $project_map = mpull($projects, null, 'getPHID'); } foreach ($diffs as $diff) { $project = null; if ($diff->getArcanistProjectPHID()) { $project = idx($project_map, $diff->getArcanistProjectPHID()); } $diff->attachArcanistProject($project); } return $diffs; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->revisionIDs) { $where[] = qsprintf( $conn_r, 'revisionID IN (%Ld)', $this->revisionIDs); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationDifferential'; + } + } diff --git a/src/applications/differential/query/DifferentialRevisionQuery.php b/src/applications/differential/query/DifferentialRevisionQuery.php index 556ec6a83c..e124801d11 100644 --- a/src/applications/differential/query/DifferentialRevisionQuery.php +++ b/src/applications/differential/query/DifferentialRevisionQuery.php @@ -1,1195 +1,1196 @@ withStatus(DifferentialRevisionQuery::STATUS_OPEN) * ->execute(); * * @task config Query Configuration * @task exec Query Execution * @task internal Internals */ final class DifferentialRevisionQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $pathIDs = array(); private $status = 'status-any'; const STATUS_ANY = 'status-any'; const STATUS_OPEN = 'status-open'; const STATUS_ACCEPTED = 'status-accepted'; const STATUS_NEEDS_REVIEW = 'status-needs-review'; const STATUS_NEEDS_REVISION = 'status-needs-revision'; const STATUS_CLOSED = 'status-closed'; // NOTE: Same as 'committed' const STATUS_COMMITTED = 'status-committed'; // TODO: Remove. const STATUS_ABANDONED = 'status-abandoned'; private $authors = array(); private $draftAuthors = array(); private $ccs = array(); private $reviewers = array(); private $revIDs = array(); private $commitHashes = array(); private $phids = array(); private $subscribers = array(); private $responsibles = array(); private $branches = array(); private $arcanistProjectPHIDs = array(); private $draftRevisions = array(); private $repositoryPHIDs; private $order = 'order-modified'; const ORDER_MODIFIED = 'order-modified'; const ORDER_CREATED = 'order-created'; /** * This is essentially a denormalized copy of the revision modified time that * should perform better for path queries with a LIMIT. Critically, when you * browse "/", every revision in that repository for all time will match so * the query benefits from being able to stop before fully materializing the * result set. */ const ORDER_PATH_MODIFIED = 'order-path-modified'; private $needRelationships = false; private $needActiveDiffs = false; private $needDiffIDs = false; private $needCommitPHIDs = false; private $needHashes = false; private $needReviewerStatus = false; private $needReviewerAuthority; private $buildingGlobalOrder; /* -( Query Configuration )------------------------------------------------ */ /** * Filter results to revisions which affect a Diffusion path ID in a given * repository. You can call this multiple times to select revisions for * several paths. * * @param int Diffusion repository ID. * @param int Diffusion path ID. * @return this * @task config */ public function withPath($repository_id, $path_id) { $this->pathIDs[] = array( 'repositoryID' => $repository_id, 'pathID' => $path_id, ); return $this; } /** * Filter results to revisions authored by one of the given PHIDs. Calling * this function will clear anything set by previous calls to * @{method:withAuthors}. * * @param array List of PHIDs of authors * @return this * @task config */ public function withAuthors(array $author_phids) { $this->authors = $author_phids; return $this; } /** * Filter results to revisions with comments authored bythe given PHIDs * * @param array List of PHIDs of authors * @return this * @task config */ public function withDraftRepliesByAuthors(array $author_phids) { $this->draftAuthors = $author_phids; return $this; } /** * Filter results to revisions which CC one of the listed people. Calling this * function will clear anything set by previous calls to @{method:withCCs}. * * @param array List of PHIDs of subscribers * @return this * @task config */ public function withCCs(array $cc_phids) { $this->ccs = $cc_phids; return $this; } /** * Filter results to revisions that have one of the provided PHIDs as * reviewers. Calling this function will clear anything set by previous calls * to @{method:withReviewers}. * * @param array List of PHIDs of reviewers * @return this * @task config */ public function withReviewers(array $reviewer_phids) { $this->reviewers = $reviewer_phids; return $this; } /** * Filter results to revisions that have one of the provided commit hashes. * Calling this function will clear anything set by previous calls to * @{method:withCommitHashes}. * * @param array List of pairs * @return this * @task config */ public function withCommitHashes(array $commit_hashes) { $this->commitHashes = $commit_hashes; return $this; } /** * Filter results to revisions with a given status. Provide a class constant, * such as ##DifferentialRevisionQuery::STATUS_OPEN##. * * @param const Class STATUS constant, like STATUS_OPEN. * @return this * @task config */ public function withStatus($status_constant) { $this->status = $status_constant; return $this; } /** * Filter results to revisions on given branches. * * @param list List of branch names. * @return this * @task config */ public function withBranches(array $branches) { $this->branches = $branches; return $this; } /** * Filter results to only return revisions whose ids are in the given set. * * @param array List of revision ids * @return this * @task config */ public function withIDs(array $ids) { $this->revIDs = $ids; return $this; } /** * Filter results to only return revisions whose PHIDs are in the given set. * * @param array List of revision PHIDs * @return this * @task config */ public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } /** * Given a set of users, filter results to return only revisions they are * responsible for (i.e., they are either authors or reviewers). * * @param array List of user PHIDs. * @return this * @task config */ public function withResponsibleUsers(array $responsible_phids) { $this->responsibles = $responsible_phids; return $this; } /** * Filter results to only return revisions with a given set of subscribers * (i.e., they are authors, reviewers or CC'd). * * @param array List of user PHIDs. * @return this * @task config */ public function withSubscribers(array $subscriber_phids) { $this->subscribers = $subscriber_phids; return $this; } /** * Filter results to only return revisions with a given set of arcanist * projects. * * @param array List of project PHIDs. * @return this * @task config */ public function withArcanistProjectPHIDs(array $arc_project_phids) { $this->arcanistProjectPHIDs = $arc_project_phids; return $this; } public function withRepositoryPHIDs(array $repository_phids) { $this->repositoryPHIDs = $repository_phids; return $this; } /** * Set result ordering. Provide a class constant, such as * ##DifferentialRevisionQuery::ORDER_CREATED##. * * @task config */ public function setOrder($order_constant) { $this->order = $order_constant; return $this; } /** * Set whether or not the query will load and attach relationships. * * @param bool True to load and attach relationships. * @return this * @task config */ public function needRelationships($need_relationships) { $this->needRelationships = $need_relationships; return $this; } /** * Set whether or not the query should load the active diff for each * revision. * * @param bool True to load and attach diffs. * @return this * @task config */ public function needActiveDiffs($need_active_diffs) { $this->needActiveDiffs = $need_active_diffs; return $this; } /** * Set whether or not the query should load the associated commit PHIDs for * each revision. * * @param bool True to load and attach diffs. * @return this * @task config */ public function needCommitPHIDs($need_commit_phids) { $this->needCommitPHIDs = $need_commit_phids; return $this; } /** * Set whether or not the query should load associated diff IDs for each * revision. * * @param bool True to load and attach diff IDs. * @return this * @task config */ public function needDiffIDs($need_diff_ids) { $this->needDiffIDs = $need_diff_ids; return $this; } /** * Set whether or not the query should load associated commit hashes for each * revision. * * @param bool True to load and attach commit hashes. * @return this * @task config */ public function needHashes($need_hashes) { $this->needHashes = $need_hashes; return $this; } /** * Set whether or not the query should load associated reviewer status. * * @param bool True to load and attach reviewers. * @return this * @task config */ public function needReviewerStatus($need_reviewer_status) { $this->needReviewerStatus = $need_reviewer_status; return $this; } /** * Request information about the viewer's authority to act on behalf of each * reviewer. In particular, they have authority to act on behalf of projects * they are a member of. * * @param bool True to load and attach authority. * @return this * @task config */ public function needReviewerAuthority($need_reviewer_authority) { $this->needReviewerAuthority = $need_reviewer_authority; return $this; } /* -( Query Execution )---------------------------------------------------- */ /** * Execute the query as configured, returning matching * @{class:DifferentialRevision} objects. * * @return list List of matching DifferentialRevision objects. * @task exec */ public function loadPage() { $table = new DifferentialRevision(); $conn_r = $table->establishConnection('r'); $data = $this->loadData(); return $table->loadAllFromArray($data); } public function willFilterPage(array $revisions) { $viewer = $this->getViewer(); $repository_phids = mpull($revisions, 'getRepositoryPHID'); $repository_phids = array_filter($repository_phids); $repositories = array(); if ($repository_phids) { $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withPHIDs($repository_phids) ->execute(); $repositories = mpull($repositories, null, 'getPHID'); } // If a revision is associated with a repository: // // - the viewer must be able to see the repository; or // - the viewer must have an automatic view capability. // // In the latter case, we'll load the revision but not load the repository. $can_view = PhabricatorPolicyCapability::CAN_VIEW; foreach ($revisions as $key => $revision) { $repo_phid = $revision->getRepositoryPHID(); if (!$repo_phid) { // The revision has no associated repository. Attach `null` and move on. $revision->attachRepository(null); continue; } $repository = idx($repositories, $repo_phid); if ($repository) { // The revision has an associated repository, and the viewer can see // it. Attach it and move on. $revision->attachRepository($repository); continue; } if ($revision->hasAutomaticCapability($can_view, $viewer)) { // The revision has an associated repository which the viewer can not // see, but the viewer has an automatic capability on this revision. // Load the revision without attaching a repository. $revision->attachRepository(null); continue; } // The revision has an associated repository, and the viewer can't see // it, and the viewer has no special capabilities. Filter out this // revision. $this->didRejectResult($revision); unset($revisions[$key]); } if (!$revisions) { return array(); } $table = new DifferentialRevision(); $conn_r = $table->establishConnection('r'); if ($this->needRelationships) { $this->loadRelationships($conn_r, $revisions); } if ($this->needCommitPHIDs) { $this->loadCommitPHIDs($conn_r, $revisions); } $need_active = $this->needActiveDiffs; $need_ids = $need_active || $this->needDiffIDs; if ($need_ids) { $this->loadDiffIDs($conn_r, $revisions); } if ($need_active) { $this->loadActiveDiffs($conn_r, $revisions); } if ($this->needHashes) { $this->loadHashes($conn_r, $revisions); } if ($this->needReviewerStatus || $this->needReviewerAuthority) { $this->loadReviewers($conn_r, $revisions); } return $revisions; } private function loadData() { $table = new DifferentialRevision(); $conn_r = $table->establishConnection('r'); if ($this->draftAuthors) { $this->draftRevisions = array(); $draft_key = 'differential-comment-'; $drafts = id(new PhabricatorDraft())->loadAllWhere( 'authorPHID IN (%Ls) AND draftKey LIKE %> AND draft != %s', $this->draftAuthors, $draft_key, ''); $len = strlen($draft_key); foreach ($drafts as $draft) { $this->draftRevisions[] = substr($draft->getDraftKey(), $len); } // TODO: Restore this after drafts are sorted out. It's now very // expensive to get revision IDs. /* $inlines = id(new DifferentialInlineCommentQuery()) ->withDraftsByAuthors($this->draftAuthors) ->execute(); foreach ($inlines as $inline) { $this->draftRevisions[] = $inline->getRevisionID(); } */ if (!$this->draftRevisions) { return array(); } } $selects = array(); // NOTE: If the query includes "responsiblePHIDs", we execute it as a // UNION of revisions they own and revisions they're reviewing. This has // much better performance than doing it with JOIN/WHERE. if ($this->responsibles) { $basic_authors = $this->authors; $basic_reviewers = $this->reviewers; $authority_projects = id(new PhabricatorProjectQuery()) ->setViewer($this->getViewer()) ->withMemberPHIDs($this->responsibles) ->execute(); $authority_phids = mpull($authority_projects, 'getPHID'); try { // Build the query where the responsible users are authors. $this->authors = array_merge($basic_authors, $this->responsibles); $this->reviewers = $basic_reviewers; $selects[] = $this->buildSelectStatement($conn_r); // Build the query where the responsible users are reviewers, or // projects they are members of are reviewers. $this->authors = $basic_authors; $this->reviewers = array_merge( $basic_reviewers, $this->responsibles, $authority_phids); $selects[] = $this->buildSelectStatement($conn_r); // Put everything back like it was. $this->authors = $basic_authors; $this->reviewers = $basic_reviewers; } catch (Exception $ex) { $this->authors = $basic_authors; $this->reviewers = $basic_reviewers; throw $ex; } } else { $selects[] = $this->buildSelectStatement($conn_r); } if (count($selects) > 1) { $this->buildingGlobalOrder = true; $query = qsprintf( $conn_r, '%Q %Q %Q', implode(' UNION DISTINCT ', $selects), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); } else { $query = head($selects); } return queryfx_all($conn_r, '%Q', $query); } private function buildSelectStatement(AphrontDatabaseConnection $conn_r) { $table = new DifferentialRevision(); $select = qsprintf( $conn_r, 'SELECT r.* FROM %T r', $table->getTableName()); $joins = $this->buildJoinsClause($conn_r); $where = $this->buildWhereClause($conn_r); $group_by = $this->buildGroupByClause($conn_r); $this->buildingGlobalOrder = false; $order_by = $this->buildOrderClause($conn_r); $limit = $this->buildLimitClause($conn_r); return qsprintf( $conn_r, '(%Q %Q %Q %Q %Q %Q)', $select, $joins, $where, $group_by, $order_by, $limit); } /* -( Internals )---------------------------------------------------------- */ /** * @task internal */ private function buildJoinsClause($conn_r) { $joins = array(); if ($this->pathIDs) { $path_table = new DifferentialAffectedPath(); $joins[] = qsprintf( $conn_r, 'JOIN %T p ON p.revisionID = r.id', $path_table->getTableName()); } if ($this->commitHashes) { $joins[] = qsprintf( $conn_r, 'JOIN %T hash_rel ON hash_rel.revisionID = r.id', ArcanistDifferentialRevisionHash::TABLE_NAME); } if ($this->ccs) { $joins[] = qsprintf( $conn_r, 'JOIN %T cc_rel ON cc_rel.revisionID = r.id '. 'AND cc_rel.relation = %s '. 'AND cc_rel.objectPHID in (%Ls)', DifferentialRevision::RELATIONSHIP_TABLE, DifferentialRevision::RELATION_SUBSCRIBED, $this->ccs); } if ($this->reviewers) { $joins[] = qsprintf( $conn_r, 'JOIN %T e_reviewers ON e_reviewers.src = r.phid '. 'AND e_reviewers.type = %s '. 'AND e_reviewers.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER, $this->reviewers); } if ($this->subscribers) { // TODO: These can be expressed as a JOIN again (and the corresponding // WHERE clause removed) once subscribers move to edges. $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T sub_rel_cc ON sub_rel_cc.revisionID = r.id '. 'AND sub_rel_cc.relation = %s '. 'AND sub_rel_cc.objectPHID in (%Ls)', DifferentialRevision::RELATIONSHIP_TABLE, DifferentialRevision::RELATION_SUBSCRIBED, $this->subscribers); $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T sub_rel_reviewer ON sub_rel_reviewer.src = r.phid '. 'AND sub_rel_reviewer.type = %s '. 'AND sub_rel_reviewer.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER, $this->subscribers); } $joins = implode(' ', $joins); return $joins; } /** * @task internal */ private function buildWhereClause($conn_r) { $where = array(); if ($this->pathIDs) { $path_clauses = array(); $repo_info = igroup($this->pathIDs, 'repositoryID'); foreach ($repo_info as $repository_id => $paths) { $path_clauses[] = qsprintf( $conn_r, '(p.repositoryID = %d AND p.pathID IN (%Ld))', $repository_id, ipull($paths, 'pathID')); } $path_clauses = '('.implode(' OR ', $path_clauses).')'; $where[] = $path_clauses; } if ($this->authors) { $where[] = qsprintf( $conn_r, 'r.authorPHID IN (%Ls)', $this->authors); } if ($this->draftRevisions) { $where[] = qsprintf( $conn_r, 'r.id IN (%Ld)', $this->draftRevisions); } if ($this->revIDs) { $where[] = qsprintf( $conn_r, 'r.id IN (%Ld)', $this->revIDs); } if ($this->repositoryPHIDs) { $where[] = qsprintf( $conn_r, 'r.repositoryPHID IN (%Ls)', $this->repositoryPHIDs); } if ($this->commitHashes) { $hash_clauses = array(); foreach ($this->commitHashes as $info) { list($type, $hash) = $info; $hash_clauses[] = qsprintf( $conn_r, '(hash_rel.type = %s AND hash_rel.hash = %s)', $type, $hash); } $hash_clauses = '('.implode(' OR ', $hash_clauses).')'; $where[] = $hash_clauses; } if ($this->phids) { $where[] = qsprintf( $conn_r, 'r.phid IN (%Ls)', $this->phids); } if ($this->branches) { $where[] = qsprintf( $conn_r, 'r.branchName in (%Ls)', $this->branches); } if ($this->arcanistProjectPHIDs) { $where[] = qsprintf( $conn_r, 'r.arcanistProjectPHID in (%Ls)', $this->arcanistProjectPHIDs); } if ($this->subscribers) { $where[] = qsprintf( $conn_r, '(sub_rel_cc.objectPHID IS NOT NULL) OR (sub_rel_reviewer.dst IS NOT NULL)'); } switch ($this->status) { case self::STATUS_ANY: break; case self::STATUS_OPEN: $where[] = qsprintf( $conn_r, 'r.status IN (%Ld)', array( ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, ArcanistDifferentialRevisionStatus::NEEDS_REVISION, ArcanistDifferentialRevisionStatus::ACCEPTED, )); break; case self::STATUS_NEEDS_REVIEW: $where[] = qsprintf( $conn_r, 'r.status IN (%Ld)', array( ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, )); break; case self::STATUS_NEEDS_REVISION: $where[] = qsprintf( $conn_r, 'r.status IN (%Ld)', array( ArcanistDifferentialRevisionStatus::NEEDS_REVISION, )); break; case self::STATUS_ACCEPTED: $where[] = qsprintf( $conn_r, 'r.status IN (%Ld)', array( ArcanistDifferentialRevisionStatus::ACCEPTED, )); break; case self::STATUS_COMMITTED: phlog( "WARNING: DifferentialRevisionQuery using deprecated ". "STATUS_COMMITTED constant. This will be removed soon. ". "Use STATUS_CLOSED."); // fallthrough case self::STATUS_CLOSED: $where[] = qsprintf( $conn_r, 'r.status IN (%Ld)', array( ArcanistDifferentialRevisionStatus::CLOSED, )); break; case self::STATUS_ABANDONED: $where[] = qsprintf( $conn_r, 'r.status IN (%Ld)', array( ArcanistDifferentialRevisionStatus::ABANDONED, )); break; default: throw new Exception( "Unknown revision status filter constant '{$this->status}'!"); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } /** * @task internal */ private function buildGroupByClause($conn_r) { $join_triggers = array_merge( $this->pathIDs, $this->ccs, $this->reviewers, $this->subscribers); $needs_distinct = (count($join_triggers) > 1); if ($needs_distinct) { return 'GROUP BY r.id'; } else { return ''; } } private function loadCursorObject($id) { $results = id(new DifferentialRevisionQuery()) ->setViewer($this->getPagingViewer()) ->withIDs(array((int)$id)) ->execute(); return head($results); } protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { $default = parent::buildPagingClause($conn_r); $before_id = $this->getBeforeID(); $after_id = $this->getAfterID(); if (!$before_id && !$after_id) { return $default; } if ($before_id) { $cursor = $this->loadCursorObject($before_id); } else { $cursor = $this->loadCursorObject($after_id); } if (!$cursor) { return null; } $columns = array(); switch ($this->order) { case self::ORDER_CREATED: return $default; case self::ORDER_MODIFIED: $columns[] = array( 'name' => 'r.dateModified', 'value' => $cursor->getDateModified(), 'type' => 'int', ); break; case self::ORDER_PATH_MODIFIED: $columns[] = array( 'name' => 'p.epoch', 'value' => $cursor->getDateCreated(), 'type' => 'int', ); break; } $columns[] = array( 'name' => 'r.id', 'value' => $cursor->getID(), 'type' => 'int', ); return $this->buildPagingClauseFromMultipleColumns( $conn_r, $columns, array( 'reversed' => (bool)($before_id xor $this->getReversePaging()), )); } protected function getPagingColumn() { $is_global = $this->buildingGlobalOrder; switch ($this->order) { case self::ORDER_MODIFIED: if ($is_global) { return 'dateModified'; } return 'r.dateModified'; case self::ORDER_CREATED: if ($is_global) { return 'id'; } return 'r.id'; case self::ORDER_PATH_MODIFIED: if (!$this->pathIDs) { throw new Exception( "To use ORDER_PATH_MODIFIED, you must specify withPath()."); } return 'p.epoch'; default: throw new Exception("Unknown query order constant '{$this->order}'."); } } private function loadRelationships($conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $relationships = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE revisionID in (%Ld) AND relation != %s ORDER BY sequence', DifferentialRevision::RELATIONSHIP_TABLE, mpull($revisions, 'getID'), DifferentialRevision::RELATION_REVIEWER); $relationships = igroup($relationships, 'revisionID'); $type_reviewer = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER; $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($revisions, 'getPHID')) ->withEdgeTypes(array($type_reviewer)) ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST) ->execute(); foreach ($revisions as $revision) { $data = idx($relationships, $revision->getID(), array()); $revision_edges = $edges[$revision->getPHID()][$type_reviewer]; foreach ($revision_edges as $dst_phid => $edge_data) { $data[] = array( 'relation' => DifferentialRevision::RELATION_REVIEWER, 'objectPHID' => $dst_phid, 'reasonPHID' => null, ); } $revision->attachRelationships($data); } } private function loadCommitPHIDs($conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $commit_phids = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE revisionID IN (%Ld)', DifferentialRevision::TABLE_COMMIT, mpull($revisions, 'getID')); $commit_phids = igroup($commit_phids, 'revisionID'); foreach ($revisions as $revision) { $phids = idx($commit_phids, $revision->getID(), array()); $phids = ipull($phids, 'commitPHID'); $revision->attachCommitPHIDs($phids); } } private function loadDiffIDs($conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $diff_table = new DifferentialDiff(); $diff_ids = queryfx_all( $conn_r, 'SELECT revisionID, id FROM %T WHERE revisionID IN (%Ld) ORDER BY id DESC', $diff_table->getTableName(), mpull($revisions, 'getID')); $diff_ids = igroup($diff_ids, 'revisionID'); foreach ($revisions as $revision) { $ids = idx($diff_ids, $revision->getID(), array()); $ids = ipull($ids, 'id'); $revision->attachDiffIDs($ids); } } private function loadActiveDiffs($conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $diff_table = new DifferentialDiff(); $load_ids = array(); foreach ($revisions as $revision) { $diffs = $revision->getDiffIDs(); if ($diffs) { $load_ids[] = max($diffs); } } $active_diffs = array(); if ($load_ids) { $active_diffs = $diff_table->loadAllWhere( 'id IN (%Ld)', $load_ids); } $active_diffs = mpull($active_diffs, null, 'getRevisionID'); foreach ($revisions as $revision) { $revision->attachActiveDiff(idx($active_diffs, $revision->getID())); } } private function loadHashes( AphrontDatabaseConnection $conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE revisionID IN (%Ld)', 'differential_revisionhash', mpull($revisions, 'getID')); $data = igroup($data, 'revisionID'); foreach ($revisions as $revision) { $hashes = idx($data, $revision->getID(), array()); $list = array(); foreach ($hashes as $hash) { $list[] = array($hash['type'], $hash['hash']); } $revision->attachHashes($list); } } private function loadReviewers( AphrontDatabaseConnection $conn_r, array $revisions) { assert_instances_of($revisions, 'DifferentialRevision'); $edge_type = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER; $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($revisions, 'getPHID')) ->withEdgeTypes(array($edge_type)) ->needEdgeData(true) ->setOrder(PhabricatorEdgeQuery::ORDER_OLDEST_FIRST) ->execute(); $viewer = $this->getViewer(); $viewer_phid = $viewer->getPHID(); // Figure out which of these reviewers the viewer has authority to act as. if ($this->needReviewerAuthority && $viewer_phid) { $allow_key = 'differential.allow-self-accept'; $allow_self = PhabricatorEnv::getEnvConfig($allow_key); $authority = $this->loadReviewerAuthority( $revisions, $edges, $allow_self); } foreach ($revisions as $revision) { $revision_edges = $edges[$revision->getPHID()][$edge_type]; $reviewers = array(); foreach ($revision_edges as $reviewer_phid => $edge) { $reviewer = new DifferentialReviewer($reviewer_phid, $edge['data']); if ($this->needReviewerAuthority) { if (!$viewer_phid) { // Logged-out users never have authority. $has_authority = false; } else if ((!$allow_self) && ($revision->getAuthorPHID() == $viewer_phid)) { // The author can never have authority unless we allow self-accept. $has_authority = false; } else { // Otherwise, look up whether th viewer has authority. $has_authority = isset($authority[$reviewer_phid]); } $reviewer->attachAuthority($viewer, $has_authority); } $reviewers[$reviewer_phid] = $reviewer; } $revision->attachReviewerStatus($reviewers); } } public static function splitResponsible(array $revisions, array $user_phids) { $blocking = array(); $active = array(); $waiting = array(); $status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW; // Bucket revisions into $blocking (revisions where you are blocking // others), $active (revisions you need to do something about) and $waiting // (revisions you're waiting on someone else to do something about). foreach ($revisions as $revision) { $needs_review = ($revision->getStatus() == $status_review); $filter_is_author = in_array($revision->getAuthorPHID(), $user_phids); if (!$revision->getReviewers()) { $needs_review = false; } // If exactly one of "needs review" and "the user is the author" is // true, the user needs to act on it. Otherwise, they're waiting on // it. if ($needs_review ^ $filter_is_author) { if ($needs_review) { array_unshift($blocking, $revision); } else { $active[] = $revision; } } else { $waiting[] = $revision; } } return array($blocking, $active, $waiting); } private function loadReviewerAuthority( array $revisions, array $edges, $allow_self) { $revision_map = mpull($revisions, null, 'getPHID'); $viewer_phid = $this->getViewer()->getPHID(); // Find all the project reviewers which the user may have authority over. $project_phids = array(); $project_type = PhabricatorProjectPHIDTypeProject::TYPECONST; $edge_type = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER; foreach ($edges as $src => $types) { if (!$allow_self) { if ($revision_map[$src]->getAuthorPHID() == $viewer_phid) { // If self-review isn't permitted, the user will never have // authority over projects on revisions they authored because you // can't accept your own revisions, so we don't need to load any // data about these reviewers. continue; } } $edge_data = idx($types, $edge_type, array()); foreach ($edge_data as $dst => $data) { if (phid_get_type($dst) == $project_type) { $project_phids[] = $dst; } } } // Now, figure out which of these projects the viewer is actually a // member of. $project_authority = array(); if ($project_phids) { $project_authority = id(new PhabricatorProjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($project_phids) ->withMemberPHIDs(array($viewer_phid)) ->execute(); $project_authority = mpull($project_authority, 'getPHID'); } // Finally, the viewer has authority over themselves. return array( $viewer_phid => true, ) + array_fuse($project_authority); } - - + public function getQueryApplicationClass() { + return 'PhabricatorApplicationDifferential'; + } } diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index 36e5a711ea..3522b6e7fa 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -1,253 +1,257 @@ identifiers = $identifiers; return $this; } /** * If a default repository is provided, ambiguous commit identifiers will * be assumed to belong to the default repository. * * For example, "r123" appearing in a commit message in repository X is * likely to be unambiguously "rX123". Normally the reference would be * considered ambiguous, but if you provide a default repository it will * be correctly resolved. */ public function withDefaultRepository(PhabricatorRepository $repository) { $this->defaultRepository = $repository; return $this; } public function withIDs(array $ids) { $this->ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function getIdentifierMap() { if ($this->identifierMap === null) { throw new Exception( "You must execute() the query before accessing the identifier map."); } return $this->identifierMap; } protected function loadPage() { if ($this->identifierMap === null) { $this->identifierMap = array(); } $table = new PhabricatorRepositoryCommit(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $commits) { $repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID'); $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withIDs($repository_ids) ->execute(); foreach ($commits as $key => $commit) { $repo = idx($repos, $commit->getRepositoryID()); if ($repo) { $commit->attachRepository($repo); } else { unset($commits[$key]); } } if ($this->identifiers !== null) { $ids = array_fuse($this->identifiers); $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH; $result = array(); foreach ($commits as $commit) { $prefix = 'r'.$commit->getRepository()->getCallsign(); $suffix = $commit->getCommitIdentifier(); if ($commit->getRepository()->isSVN()) { if (isset($ids[$prefix.$suffix])) { $result[$prefix.$suffix][] = $commit; } } else { // This awkward contruction is so we can link the commits up in O(N) // time instead of O(N^2). for ($ii = $min_qualified; $ii <= strlen($suffix); $ii++) { $part = substr($suffix, 0, $ii); if (isset($ids[$prefix.$part])) { $result[$prefix.$part][] = $commit; } if (isset($ids[$part])) { $result[$part][] = $commit; } } } } foreach ($result as $identifier => $matching_commits) { if (count($matching_commits) == 1) { $result[$identifier] = head($matching_commits); } else { // This reference is ambiguous (it matches more than one commit) so // don't link it unset($result[$identifier]); } } $this->identifierMap += $result; } return $commits; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->identifiers) { $min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH; $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH; $refs = array(); $bare = array(); foreach ($this->identifiers as $identifier) { $matches = null; preg_match('/^(?:r([A-Z]+))?(.*)$/', $identifier, $matches); $repo = nonempty($matches[1], null); $identifier = nonempty($matches[2], null); if ($repo === null) { if ($this->defaultRepository) { $repo = $this->defaultRepository->getCallsign(); } } if ($repo === null) { if (strlen($identifier) < $min_unqualified) { continue; } $bare[] = $identifier; } else { $refs[] = array( 'callsign' => $repo, 'identifier' => $identifier, ); } } $sql = array(); foreach ($bare as $identifier) { $sql[] = qsprintf( $conn_r, '(commitIdentifier LIKE %> AND LENGTH(commitIdentifier) = 40)', $identifier); } if ($refs) { $callsigns = ipull($refs, 'callsign'); $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withCallsigns($callsigns) ->execute(); $repos = mpull($repos, null, 'getCallsign'); foreach ($refs as $key => $ref) { $repo = idx($repos, $ref['callsign']); if (!$repo) { continue; } if ($repo->isSVN()) { if (!ctype_digit($ref['identifier'])) { continue; } $sql[] = qsprintf( $conn_r, '(repositoryID = %d AND commitIdentifier = %s)', $repo->getID(), // NOTE: Because the 'commitIdentifier' column is a string, MySQL // ignores the index if we hand it an integer. Hand it a string. // See T3377. (int)$ref['identifier']); } else { if (strlen($ref['identifier']) < $min_qualified) { continue; } $sql[] = qsprintf( $conn_r, '(repositoryID = %d AND commitIdentifier LIKE %>)', $repo->getID(), $ref['identifier']); } } } if (!$sql) { // If we discarded all possible identifiers (e.g., they all referenced // bogus repositories or were all too short), make sure the query finds // nothing. throw new PhabricatorEmptyQueryException('No commit identifiers.'); } $where[] = '('.implode(' OR ', $sql).')'; } if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } return $this->formatWhereClause($where); } public function didFilterResults(array $filtered) { if ($this->identifierMap) { foreach ($this->identifierMap as $name => $commit) { if (isset($filtered[$commit->getPHID()])) { unset($this->identifierMap[$name]); } } } } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationDiffusion'; + } + } diff --git a/src/applications/diviner/phid/DivinerPHIDTypeAtom.php b/src/applications/diviner/phid/DivinerPHIDTypeAtom.php index a563ec45c6..6a057c0741 100644 --- a/src/applications/diviner/phid/DivinerPHIDTypeAtom.php +++ b/src/applications/diviner/phid/DivinerPHIDTypeAtom.php @@ -1,43 +1,40 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $atom = $objects[$phid]; $handle->setName($atom->getTitle()); $handle->setURI($atom->getName()); } } } diff --git a/src/applications/diviner/phid/DivinerPHIDTypeBook.php b/src/applications/diviner/phid/DivinerPHIDTypeBook.php index 2d80555371..3bd965ee78 100644 --- a/src/applications/diviner/phid/DivinerPHIDTypeBook.php +++ b/src/applications/diviner/phid/DivinerPHIDTypeBook.php @@ -1,46 +1,43 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $book = $objects[$phid]; $name = $book->getName(); $handle->setName($book->getShortTitle()); $handle->setFullName($book->getTitle()); $handle->setURI("/diviner/book/{$name}/"); } } } diff --git a/src/applications/diviner/query/DivinerAtomQuery.php b/src/applications/diviner/query/DivinerAtomQuery.php index 662bc834d8..45986b7674 100644 --- a/src/applications/diviner/query/DivinerAtomQuery.php +++ b/src/applications/diviner/query/DivinerAtomQuery.php @@ -1,408 +1,412 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withBookPHIDs(array $phids) { $this->bookPHIDs = $phids; return $this; } public function withTypes(array $types) { $this->types = $types; return $this; } public function withNames(array $names) { $this->names = $names; return $this; } public function withContexts(array $contexts) { $this->contexts = $contexts; return $this; } public function withIndexes(array $indexes) { $this->indexes = $indexes; return $this; } public function withNodeHashes(array $hashes) { $this->nodeHashes = $hashes; return $this; } public function needAtoms($need) { $this->needAtoms = $need; return $this; } public function needChildren($need) { $this->needChildren = $need; return $this; } /** * Include "ghosts", which are symbols which used to exist but do not exist * currently (for example, a function which existed in an older version of * the codebase but was deleted). * * These symbols had PHIDs assigned to them, and may have other sorts of * metadata that we don't want to lose (like comments or flags), so we don't * delete them outright. They might also come back in the future: the change * which deleted the symbol might be reverted, or the documentation might * have been generated incorrectly by accident. In these cases, we can * restore the original data. * * However, most callers are not interested in these symbols, so they are * excluded by default. You can use this method to include them in results. * * @param bool True to include ghosts. * @return this */ public function withIncludeGhosts($include) { $this->includeGhosts = $include; return $this; } public function needExtends($need) { $this->needExtends = $need; return $this; } public function withIncludeUndocumentable($include) { $this->includeUndocumentable = $include; return $this; } protected function loadPage() { $table = new DivinerLiveSymbol(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } protected function willFilterPage(array $atoms) { $books = array_unique(mpull($atoms, 'getBookPHID')); $books = id(new DivinerBookQuery()) ->setViewer($this->getViewer()) ->withPHIDs($books) ->execute(); $books = mpull($books, null, 'getPHID'); foreach ($atoms as $key => $atom) { $book = idx($books, $atom->getBookPHID()); if (!$book) { unset($atoms[$key]); continue; } $atom->attachBook($book); } if ($this->needAtoms) { $atom_data = id(new DivinerLiveAtom())->loadAllWhere( 'symbolPHID IN (%Ls)', mpull($atoms, 'getPHID')); $atom_data = mpull($atom_data, null, 'getSymbolPHID'); foreach ($atoms as $key => $atom) { $data = idx($atom_data, $atom->getPHID()); if (!$data) { unset($atoms[$key]); continue; } $atom->attachAtom($data); } } // Load all of the symbols this symbol extends, recursively. Commonly, // this means all the ancestor classes and interfaces it extends and // implements. if ($this->needExtends) { // First, load all the matching symbols by name. This does 99% of the // work in most cases, assuming things are named at all reasonably. $names = array(); foreach ($atoms as $atom) { foreach ($atom->getAtom()->getExtends() as $xref) { $names[] = $xref->getName(); } } if ($names) { $xatoms = id(new DivinerAtomQuery()) ->setViewer($this->getViewer()) ->withNames($names) ->needExtends(true) ->needAtoms(true) ->needChildren($this->needChildren) ->execute(); $xatoms = mgroup($xatoms, 'getName', 'getType', 'getBookPHID'); } else { $xatoms = array(); } foreach ($atoms as $atom) { $alang = $atom->getAtom()->getLanguage(); $extends = array(); foreach ($atom->getAtom()->getExtends() as $xref) { // If there are no symbols of the matching name and type, we can't // resolve this. if (empty($xatoms[$xref->getName()][$xref->getType()])) { continue; } // If we found matches in the same documentation book, prefer them // over other matches. Otherwise, look at all the the matches. $matches = $xatoms[$xref->getName()][$xref->getType()]; if (isset($matches[$atom->getBookPHID()])) { $maybe = $matches[$atom->getBookPHID()]; } else { $maybe = array_mergev($matches); } if (!$maybe) { continue; } // Filter out matches in a different language, since, e.g., PHP // classes can not implement JS classes. $same_lang = array(); foreach ($maybe as $xatom) { if ($xatom->getAtom()->getLanguage() == $alang) { $same_lang[] = $xatom; } } if (!$same_lang) { continue; } // If we have duplicates remaining, just pick the first one. There's // nothing more we can do to figure out which is the real one. $extends[] = head($same_lang); } $atom->attachExtends($extends); } } if ($this->needChildren) { $child_hashes = $this->getAllChildHashes($atoms, $this->needExtends); if ($child_hashes) { $children = id(new DivinerAtomQuery()) ->setViewer($this->getViewer()) ->withIncludeUndocumentable(true) ->withNodeHashes($child_hashes) ->needAtoms($this->needAtoms) ->execute(); $children = mpull($children, null, 'getNodeHash'); } else { $children = array(); } $this->attachAllChildren($atoms, $children, $this->needExtends); } return $atoms; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->bookPHIDs) { $where[] = qsprintf( $conn_r, 'bookPHID IN (%Ls)', $this->bookPHIDs); } if ($this->types) { $where[] = qsprintf( $conn_r, 'type IN (%Ls)', $this->types); } if ($this->names) { $where[] = qsprintf( $conn_r, 'name IN (%Ls)', $this->names); } if ($this->contexts) { $with_null = false; $contexts = $this->contexts; foreach ($contexts as $key => $value) { if ($value === null) { unset($contexts[$key]); $with_null = true; continue; } } if ($contexts && $with_null) { $where[] = qsprintf( $conn_r, 'context IN (%Ls) OR context IS NULL', $contexts); } else if ($contexts) { $where[] = qsprintf( $conn_r, 'context IN (%Ls)', $contexts); } else if ($with_null) { $where[] = qsprintf( $conn_r, 'context IS NULL'); } } if ($this->indexes) { $where[] = qsprintf( $conn_r, 'atomIndex IN (%Ld)', $this->indexes); } if (!$this->includeUndocumentable) { $where[] = qsprintf( $conn_r, 'isDocumentable = 1'); } if (!$this->includeGhosts) { $where[] = qsprintf( $conn_r, 'graphHash IS NOT NULL'); } if ($this->nodeHashes) { $where[] = qsprintf( $conn_r, 'nodeHash IN (%Ls)', $this->nodeHashes); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } /** * Walk a list of atoms and collect all the node hashes of the atoms' * children. When recursing, also walk up the tree and collect children of * atoms they extend. * * @param list List of symbols to collect child hashes of. * @param bool True to collect children of extended atoms, * as well. * @return map Hashes of atoms' children. */ private function getAllChildHashes(array $symbols, $recurse_up) { assert_instances_of($symbols, 'DivinerLiveSymbol'); $hashes = array(); foreach ($symbols as $symbol) { foreach ($symbol->getAtom()->getChildHashes() as $hash) { $hashes[$hash] = $hash; } if ($recurse_up) { $hashes += $this->getAllChildHashes($symbol->getExtends(), true); } } return $hashes; } /** * Attach child atoms to existing atoms. In recursive mode, also attach child * atoms to atoms that these atoms extend. * * @param list List of symbols to attach childeren to. * @param map Map of symbols, keyed by node hash. * @param bool True to attach children to extended atoms, as well. * @return void */ private function attachAllChildren( array $symbols, array $children, $recurse_up) { assert_instances_of($symbols, 'DivinerLiveSymbol'); assert_instances_of($children, 'DivinerLiveSymbol'); foreach ($symbols as $symbol) { $symbol_children = array(); foreach ($symbol->getAtom()->getChildHashes() as $hash) { if (isset($children[$hash])) { $symbol_children[] = $children[$hash]; } } $symbol->attachChildren($symbol_children); if ($recurse_up) { $this->attachAllChildren($symbol->getExtends(), $children, true); } } } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationDiviner'; + } + } diff --git a/src/applications/diviner/query/DivinerBookQuery.php b/src/applications/diviner/query/DivinerBookQuery.php index af6fe1d717..062bc524ca 100644 --- a/src/applications/diviner/query/DivinerBookQuery.php +++ b/src/applications/diviner/query/DivinerBookQuery.php @@ -1,69 +1,74 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withNames(array $names) { $this->names = $names; return $this; } protected function loadPage() { $table = new DivinerLiveBook(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->names) { $where[] = qsprintf( $conn_r, 'name IN (%Ls)', $this->names); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationDiviner'; + } + } diff --git a/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php b/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php index 0246fdf210..9c410b99b6 100644 --- a/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php +++ b/src/applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php @@ -1,55 +1,59 @@ phids = $phids; return $this; } public function withObjectKeys(array $keys) { $this->objectKeys = $keys; return $this; } public function loadPage() { $table = new DoorkeeperExternalObject(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->objectKeys) { $where[] = qsprintf( $conn_r, 'objectKey IN (%Ls)', $this->objectKeys); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationDoorkeeper'; + } + } diff --git a/src/applications/feed/query/PhabricatorFeedQuery.php b/src/applications/feed/query/PhabricatorFeedQuery.php index 44a677faa1..2d324f2dcc 100644 --- a/src/applications/feed/query/PhabricatorFeedQuery.php +++ b/src/applications/feed/query/PhabricatorFeedQuery.php @@ -1,107 +1,112 @@ filterPHIDs = $phids; return $this; } public function withChronologicalKeys(array $keys) { $this->chronologicalKeys = $keys; return $this; } protected function loadPage() { $story_table = new PhabricatorFeedStoryData(); $conn = $story_table->establishConnection('r'); $data = queryfx_all( $conn, 'SELECT story.* FROM %T story %Q %Q %Q %Q %Q', $story_table->getTableName(), $this->buildJoinClause($conn), $this->buildWhereClause($conn), $this->buildGroupClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); return $data; } protected function willFilterPage(array $data) { return PhabricatorFeedStory::loadAllFromRows($data, $this->getViewer()); } private function buildJoinClause(AphrontDatabaseConnection $conn_r) { // NOTE: We perform this join unconditionally (even if we have no filter // PHIDs) to omit rows which have no story references. These story data // rows are notifications or realtime alerts. $ref_table = new PhabricatorFeedStoryReference(); return qsprintf( $conn_r, 'JOIN %T ref ON ref.chronologicalKey = story.chronologicalKey', $ref_table->getTableName()); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->filterPHIDs) { $where[] = qsprintf( $conn_r, 'ref.objectPHID IN (%Ls)', $this->filterPHIDs); } if ($this->chronologicalKeys) { // NOTE: We want to use integers in the query so we can take advantage // of keys, but can't use %d on 32-bit systems. Make sure all the keys // are integers and then format them raw. $keys = $this->chronologicalKeys; foreach ($keys as $key) { if (!ctype_digit($key)) { throw new Exception("Key '{$key}' is not a valid chronological key!"); } } $where[] = qsprintf( $conn_r, 'ref.chronologicalKey IN (%Q)', implode(', ', $keys)); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function buildGroupClause(AphrontDatabaseConnection $conn_r) { return qsprintf( $conn_r, 'GROUP BY '.($this->filterPHIDs ? 'ref.chronologicalKey' : 'story.chronologicalKey')); } protected function getPagingColumn() { return ($this->filterPHIDs ? 'ref.chronologicalKey' : 'story.chronologicalKey'); } protected function getPagingValue($item) { if ($item instanceof PhabricatorFeedStory) { return $item->getChronologicalKey(); } return $item['chronologicalKey']; } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationFeed'; + } + } diff --git a/src/applications/files/phid/PhabricatorFilePHIDTypeFile.php b/src/applications/files/phid/PhabricatorFilePHIDTypeFile.php index 50586bee78..1f6fe80df6 100644 --- a/src/applications/files/phid/PhabricatorFilePHIDTypeFile.php +++ b/src/applications/files/phid/PhabricatorFilePHIDTypeFile.php @@ -1,77 +1,74 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $file = $objects[$phid]; $id = $file->getID(); $name = $file->getName(); $uri = $file->getBestURI(); $handle->setName("F{$id}"); $handle->setFullName("F{$id}: {$name}"); $handle->setURI($uri); } } public function canLoadNamedObject($name) { return preg_match('/^F\d*[1-9]\d*$/', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new PhabricatorFileQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/files/query/PhabricatorFileQuery.php b/src/applications/files/query/PhabricatorFileQuery.php index 28393b4502..854f49411e 100644 --- a/src/applications/files/query/PhabricatorFileQuery.php +++ b/src/applications/files/query/PhabricatorFileQuery.php @@ -1,238 +1,243 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $phids) { $this->authorPHIDs = $phids; return $this; } public function withDateCreatedBefore($date_created_before) { $this->dateCreatedBefore = $date_created_before; return $this; } public function withDateCreatedAfter($date_created_after) { $this->dateCreatedAfter = $date_created_after; return $this; } /** * Select files which are transformations of some other file. For example, * you can use this query to find previously generated thumbnails of an image * file. * * As a parameter, provide a list of transformation specifications. Each * specification is a dictionary with the keys `originalPHID` and `transform`. * The `originalPHID` is the PHID of the original file (the file which was * transformed) and the `transform` is the name of the transform to query * for. If you pass `true` as the `transform`, all transformations of the * file will be selected. * * For example: * * array( * array( * 'originalPHID' => 'PHID-FILE-aaaa', * 'transform' => 'sepia', * ), * array( * 'originalPHID' => 'PHID-FILE-bbbb', * 'transform' => true, * ), * ) * * This selects the `"sepia"` transformation of the file with PHID * `PHID-FILE-aaaa` and all transformations of the file with PHID * `PHID-FILE-bbbb`. * * @param list List of transform specifications, described above. * @return this */ public function withTransforms(array $specs) { foreach ($specs as $spec) { if (!is_array($spec) || empty($spec['originalPHID']) || empty($spec['transform'])) { throw new Exception( "Transform specification must be a dictionary with keys ". "'originalPHID' and 'transform'!"); } } $this->transforms = $specs; return $this; } public function showOnlyExplicitUploads($explicit_uploads) { $this->explicitUploads = $explicit_uploads; return $this; } protected function loadPage() { $table = new PhabricatorFile(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT f.* FROM %T f %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $files = $table->loadAllFromArray($data); if (!$files) { return $files; } // We need to load attached objects to perform policy checks for files. // First, load the edges. $edge_type = PhabricatorEdgeConfig::TYPE_FILE_HAS_OBJECT; $phids = mpull($files, 'getPHID'); $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs($phids) ->withEdgeTypes(array($edge_type)) ->execute(); $object_phids = array(); foreach ($files as $file) { $phids = array_keys($edges[$file->getPHID()][$edge_type]); $file->attachObjectPHIDs($phids); foreach ($phids as $phid) { $object_phids[$phid] = true; } } $object_phids = array_keys($object_phids); // Now, load the objects. $objects = array(); if ($object_phids) { $objects = id(new PhabricatorObjectQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($object_phids) ->execute(); $objects = mpull($objects, null, 'getPHID'); } foreach ($files as $file) { $file_objects = array_select_keys($objects, $file->getObjectPHIDs()); $file->attachObjects($file_objects); } return $files; } private function buildJoinClause(AphrontDatabaseConnection $conn_r) { $joins = array(); if ($this->transforms) { $joins[] = qsprintf( $conn_r, 'JOIN %T t ON t.transformedPHID = f.phid', id(new PhabricatorTransformedFile())->getTableName()); } return implode(' ', $joins); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids) { $where[] = qsprintf( $conn_r, 'f.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'f.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'f.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->explicitUploads) { $where[] = qsprintf( $conn_r, 'f.isExplicitUpload = true'); } if ($this->transforms) { $clauses = array(); foreach ($this->transforms as $transform) { if ($transform['transform'] === true) { $clauses[] = qsprintf( $conn_r, '(t.originalPHID = %s)', $transform['originalPHID']); } else { $clauses[] = qsprintf( $conn_r, '(t.originalPHID = %s AND t.transform = %s)', $transform['originalPHID'], $transform['transform']); } } $where[] = qsprintf($conn_r, '(%Q)', implode(') OR (', $clauses)); } if ($this->dateCreatedAfter) { $where[] = qsprintf( $conn_r, 'f.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore) { $where[] = qsprintf( $conn_r, 'f.dateCreated <= %d', $this->dateCreatedBefore); } return $this->formatWhereClause($where); } protected function getPagingColumn() { return 'f.id'; } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationFiles'; + } + } diff --git a/src/applications/flag/query/PhabricatorFlagQuery.php b/src/applications/flag/query/PhabricatorFlagQuery.php index 58b1bc4609..c31e4a02c7 100644 --- a/src/applications/flag/query/PhabricatorFlagQuery.php +++ b/src/applications/flag/query/PhabricatorFlagQuery.php @@ -1,163 +1,168 @@ ownerPHIDs = $owner_phids; return $this; } public function withTypes(array $types) { $this->types = $types; return $this; } public function withObjectPHIDs(array $object_phids) { $this->objectPHIDs = $object_phids; return $this; } public function withColors(array $colors) { $this->colors = $colors; return $this; } /** * Note this is done in php and not in mySQL, which means its inappropriate * for large datasets. Pragmatically, this is fine for user flags which are * typically well under 100 flags per user. */ public function setGroupBy($group) { $this->groupBy = $group; return $this; } public function needHandles($need) { $this->needHandles = $need; return $this; } public function needObjects($need) { $this->needObjects = $need; return $this; } public static function loadUserFlag(PhabricatorUser $user, $object_phid) { // Specifying the type in the query allows us to use a key. return id(new PhabricatorFlagQuery()) ->setViewer($user) ->withOwnerPHIDs(array($user->getPHID())) ->withTypes(array(phid_get_type($object_phid))) ->withObjectPHIDs(array($object_phid)) ->executeOne(); } public function loadPage() { $table = new PhabricatorFlag(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T flag %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $flags) { if ($this->needObjects) { $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs(mpull($flags, 'getObjectPHID')) ->execute(); $objects = mpull($objects, null, 'getPHID'); foreach ($flags as $key => $flag) { $object = idx($objects, $flag->getObjectPHID()); if ($object) { $flags[$key]->attachObject($object); } else { unset($flags[$key]); } } } if ($this->needHandles) { $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->getViewer()) ->withPHIDs(mpull($flags, 'getObjectPHID')) ->execute(); foreach ($flags as $flag) { $flag->attachHandle($handles[$flag->getObjectPHID()]); } } switch ($this->groupBy) { case self::GROUP_COLOR: $flags = msort($flags, 'getColor'); break; case self::GROUP_NONE: break; default: throw new Exception("Unknown groupBy parameter: $this->groupBy"); break; } return $flags; } private function buildWhereClause($conn_r) { $where = array(); if ($this->ownerPHIDs) { $where[] = qsprintf( $conn_r, 'flag.ownerPHID IN (%Ls)', $this->ownerPHIDs); } if ($this->types) { $where[] = qsprintf( $conn_r, 'flag.type IN (%Ls)', $this->types); } if ($this->objectPHIDs) { $where[] = qsprintf( $conn_r, 'flag.objectPHID IN (%Ls)', $this->objectPHIDs); } if ($this->colors) { $where[] = qsprintf( $conn_r, 'flag.color IN (%Ld)', $this->colors); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationFlags'; + } + } diff --git a/src/applications/herald/phid/HeraldPHIDTypeRule.php b/src/applications/herald/phid/HeraldPHIDTypeRule.php index 1c408cbb46..a3022d3c0f 100644 --- a/src/applications/herald/phid/HeraldPHIDTypeRule.php +++ b/src/applications/herald/phid/HeraldPHIDTypeRule.php @@ -1,46 +1,43 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $rule = $objects[$phid]; $id = $rule->getID(); $name = $rule->getName(); $handle->setName($name); $handle->setURI("/herald/rule/{$id}/"); } } } diff --git a/src/applications/herald/query/HeraldRuleQuery.php b/src/applications/herald/query/HeraldRuleQuery.php index 374bcc0c29..e9d3d05d5f 100644 --- a/src/applications/herald/query/HeraldRuleQuery.php +++ b/src/applications/herald/query/HeraldRuleQuery.php @@ -1,232 +1,237 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $author_phids) { $this->authorPHIDs = $author_phids; return $this; } public function withRuleTypes(array $types) { $this->ruleTypes = $types; return $this; } public function withContentTypes(array $types) { $this->contentTypes = $types; return $this; } public function withExecutableRules($executable) { $this->executable = $executable; return $this; } public function withDisabled($disabled) { $this->disabled = $disabled; return $this; } public function needConditionsAndActions($need) { $this->needConditionsAndActions = $need; return $this; } public function needAppliedToPHIDs(array $phids) { $this->needAppliedToPHIDs = $phids; return $this; } public function needValidateAuthors($need) { $this->needValidateAuthors = $need; return $this; } public function loadPage() { $table = new HeraldRule(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT rule.* FROM %T rule %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $rules) { $rule_ids = mpull($rules, 'getID'); // Filter out any rules that have invalid adapters, or have adapters the // viewer isn't permitted to see or use (for example, Differential rules // if the user can't use Differential or Differential is not installed). $types = HeraldAdapter::getEnabledAdapterMap($this->getViewer()); foreach ($rules as $key => $rule) { if (empty($types[$rule->getContentType()])) { $this->didRejectResult($rule); unset($rules[$key]); } } if ($this->needValidateAuthors) { $this->validateRuleAuthors($rules); } if ($this->needConditionsAndActions) { $conditions = id(new HeraldCondition())->loadAllWhere( 'ruleID IN (%Ld)', $rule_ids); $conditions = mgroup($conditions, 'getRuleID'); $actions = id(new HeraldAction())->loadAllWhere( 'ruleID IN (%Ld)', $rule_ids); $actions = mgroup($actions, 'getRuleID'); foreach ($rules as $rule) { $rule->attachActions(idx($actions, $rule->getID(), array())); $rule->attachConditions(idx($conditions, $rule->getID(), array())); } } if ($this->needAppliedToPHIDs) { $conn_r = id(new HeraldRule())->establishConnection('r'); $applied = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE ruleID IN (%Ld) AND phid IN (%Ls)', HeraldRule::TABLE_RULE_APPLIED, $rule_ids, $this->needAppliedToPHIDs); $map = array(); foreach ($applied as $row) { $map[$row['ruleID']][$row['phid']] = true; } foreach ($rules as $rule) { foreach ($this->needAppliedToPHIDs as $phid) { $rule->setRuleApplied( $phid, isset($map[$rule->getID()][$phid])); } } } return $rules; } private function buildWhereClause($conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'rule.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'rule.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'rule.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->ruleTypes) { $where[] = qsprintf( $conn_r, 'rule.ruleType IN (%Ls)', $this->ruleTypes); } if ($this->contentTypes) { $where[] = qsprintf( $conn_r, 'rule.contentType IN (%Ls)', $this->contentTypes); } if ($this->disabled !== null) { $where[] = qsprintf( $conn_r, 'rule.isDisabled = %d', (int)$this->disabled); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function validateRuleAuthors(array $rules) { // "Global" rules always have valid authors. foreach ($rules as $key => $rule) { if ($rule->isGlobalRule()) { $rule->attachValidAuthor(true); unset($rules[$key]); continue; } } if (!$rules) { return; } // For personal rules, the author needs to exist and not be disabled. $user_phids = mpull($rules, 'getAuthorPHID'); $users = id(new PhabricatorPeopleQuery()) ->setViewer($this->getViewer()) ->withPHIDs($user_phids) ->execute(); $users = mpull($users, null, 'getPHID'); foreach ($rules as $key => $rule) { $author_phid = $rule->getAuthorPHID(); if (empty($users[$author_phid])) { $rule->attachValidAuthor(false); continue; } if ($users[$author_phid]->getIsDisabled()) { $rule->attachValidAuthor(false); continue; } $rule->attachValidAuthor(true); $rule->attachAuthor($users[$author_phid]); } } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHerald'; + } + } diff --git a/src/applications/herald/query/HeraldTranscriptQuery.php b/src/applications/herald/query/HeraldTranscriptQuery.php index 8399218c61..258b2ea898 100644 --- a/src/applications/herald/query/HeraldTranscriptQuery.php +++ b/src/applications/herald/query/HeraldTranscriptQuery.php @@ -1,98 +1,101 @@ ids = $ids; return $this; } public function needPartialRecords($need_partial) { $this->needPartialRecords = $need_partial; return $this; } public function loadPage() { $transcript = new HeraldTranscript(); $conn_r = $transcript->establishConnection('r'); // NOTE: Transcripts include a potentially enormous amount of serialized // data, so we're loading only some of the fields here if the caller asked // for partial records. if ($this->needPartialRecords) { $fields = implode( ', ', array( 'id', 'phid', 'objectPHID', 'time', 'duration', 'dryRun', 'host', )); } else { $fields = '*'; } $rows = queryfx_all( $conn_r, 'SELECT %Q FROM %T t %Q %Q %Q', $fields, $transcript->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $transcripts = $transcript->loadAllFromArray($rows); if ($this->needPartialRecords) { // Make sure nothing tries to write these; they aren't complete. foreach ($transcripts as $transcript) { $transcript->makeEphemeral(); } } return $transcripts; } public function willFilterPage(array $transcripts) { $phids = mpull($transcripts, 'getObjectPHID'); $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($phids) ->execute(); foreach ($transcripts as $key => $transcript) { if (empty($objects[$transcript->getObjectPHID()])) { $this->didRejectResult($transcript); unset($transcripts[$key]); } } return $transcripts; } public function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHerald'; + } } diff --git a/src/applications/legalpad/phid/PhabricatorLegalpadPHIDTypeDocument.php b/src/applications/legalpad/phid/PhabricatorLegalpadPHIDTypeDocument.php index d1e1fcc8cc..ad76e1f334 100644 --- a/src/applications/legalpad/phid/PhabricatorLegalpadPHIDTypeDocument.php +++ b/src/applications/legalpad/phid/PhabricatorLegalpadPHIDTypeDocument.php @@ -1,77 +1,74 @@ setViewer($query->getViewer()) - ->setParentQuery($query) ->withPHIDs($phids) - ->needDocumentBodies(true) - ->execute(); + ->needDocumentBodies(true); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $document = $objects[$phid]; $name = $document->getDocumentBody()->getTitle(); $handle->setName($name); $handle->setFullName($name); $handle->setURI('/legalpad/view/'.$document->getID().'/'); } } public function canLoadNamedObject($name) { return preg_match('/^L\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new LegalpadDocumentQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/legalpad/query/LegalpadDocumentQuery.php b/src/applications/legalpad/query/LegalpadDocumentQuery.php index ccca424de9..22db1808b0 100644 --- a/src/applications/legalpad/query/LegalpadDocumentQuery.php +++ b/src/applications/legalpad/query/LegalpadDocumentQuery.php @@ -1,185 +1,188 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withCreatorPHIDs(array $phids) { $this->creatorPHIDs = $phids; return $this; } public function withContributorPHIDs(array $phids) { $this->contributorPHIDs = $phids; return $this; } public function needDocumentBodies($need_bodies) { $this->needDocumentBodies = $need_bodies; return $this; } public function needContributors($need_contributors) { $this->needContributors = $need_contributors; return $this; } public function withDateCreatedBefore($date_created_before) { $this->dateCreatedBefore = $date_created_before; return $this; } public function withDateCreatedAfter($date_created_after) { $this->dateCreatedAfter = $date_created_after; return $this; } protected function loadPage() { $table = new LegalpadDocument(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT d.* FROM %T d %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $documents = $table->loadAllFromArray($data); return $documents; } protected function willFilterPage(array $documents) { if ($this->needDocumentBodies) { $documents = $this->loadDocumentBodies($documents); } if ($this->needContributors) { $documents = $this->loadContributors($documents); } return $documents; } private function buildJoinClause($conn_r) { $joins = array(); if ($this->contributorPHIDs) { $joins[] = qsprintf( $conn_r, 'JOIN edge e ON e.src = d.phid'); } return implode(' ', $joins); } protected function buildWhereClause($conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids) { $where[] = qsprintf( $conn_r, 'd.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'd.phid IN (%Ls)', $this->phids); } if ($this->creatorPHIDs) { $where[] = qsprintf( $conn_r, 'd.creatorPHID IN (%Ls)', $this->creatorPHIDs); } if ($this->dateCreatedAfter) { $where[] = qsprintf( $conn_r, 'd.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore) { $where[] = qsprintf( $conn_r, 'd.dateCreated <= %d', $this->dateCreatedBefore); } if ($this->contributorPHIDs) { $where[] = qsprintf( $conn_r, 'e.type = %s AND e.dst IN (%Ls)', PhabricatorEdgeConfig::TYPE_OBJECT_HAS_CONTRIBUTOR, $this->contributorPHIDs); } return $this->formatWhereClause($where); } private function loadDocumentBodies(array $documents) { $body_phids = mpull($documents, 'getDocumentBodyPHID'); $bodies = id(new LegalpadDocumentBody())->loadAllWhere( 'phid IN (%Ls)', $body_phids); $bodies = mpull($bodies, null, 'getPHID'); foreach ($documents as $document) { $body = idx($bodies, $document->getDocumentBodyPHID()); $document->attachDocumentBody($body); } return $documents; } private function loadContributors(array $documents) { $document_map = mpull($documents, null, 'getPHID'); $edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_CONTRIBUTOR; $contributor_data = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array_keys($document_map)) ->withEdgeTypes(array($edge_type)) ->execute(); foreach ($document_map as $document_phid => $document) { $data = $contributor_data[$document_phid]; $contributors = array_keys(idx($data, $edge_type, array())); $document->attachContributors($contributors); } return $documents; } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationLegalpad'; + } } diff --git a/src/applications/macro/phid/PhabricatorMacroPHIDTypeMacro.php b/src/applications/macro/phid/PhabricatorMacroPHIDTypeMacro.php index bc9b7a8d61..b5fbc351b7 100644 --- a/src/applications/macro/phid/PhabricatorMacroPHIDTypeMacro.php +++ b/src/applications/macro/phid/PhabricatorMacroPHIDTypeMacro.php @@ -1,51 +1,48 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $macro = $objects[$phid]; $id = $macro->getID(); $name = $macro->getName(); $handle->setName($name); $handle->setFullName(pht('Image Macro "%s"', $name)); $handle->setURI("/macro/view/{$id}/"); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/macro/query/PhabricatorMacroQuery.php b/src/applications/macro/query/PhabricatorMacroQuery.php index da7dbd7b83..6ebfb50f9c 100644 --- a/src/applications/macro/query/PhabricatorMacroQuery.php +++ b/src/applications/macro/query/PhabricatorMacroQuery.php @@ -1,219 +1,223 @@ pht('Active Macros'), self::STATUS_DISABLED => pht('Disabled Macros'), self::STATUS_ANY => pht('Active and Disabled Macros'), ); } public static function getFlagColorsOptions() { $options = array('-1' => pht('(No Filtering)')); foreach (PhabricatorFlagColor::getColorNameMap() as $color => $name) { $options[$color] = $name; } return $options; } public function withIDs(array $ids) { $this->ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $authors) { $this->authors = $authors; return $this; } public function withNameLike($name) { $this->nameLike = $name; return $this; } public function withNames(array $names) { $this->names = $names; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withDateCreatedBefore($date_created_before) { $this->dateCreatedBefore = $date_created_before; return $this; } public function withDateCreatedAfter($date_created_after) { $this->dateCreatedAfter = $date_created_after; return $this; } public function withFlagColor($flag_color) { $this->flagColor = $flag_color; return $this; } protected function loadPage() { $macro_table = new PhabricatorFileImageMacro(); $conn = $macro_table->establishConnection('r'); $rows = queryfx_all( $conn, 'SELECT m.* FROM %T m %Q %Q %Q', $macro_table->getTableName(), $this->buildWhereClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); return $macro_table->loadAllFromArray($rows); } protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn, 'm.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn, 'm.phid IN (%Ls)', $this->phids); } if ($this->authors) { $where[] = qsprintf( $conn, 'm.authorPHID IN (%Ls)', $this->authors); } if ($this->nameLike) { $where[] = qsprintf( $conn, 'm.name LIKE %~', $this->nameLike); } if ($this->names) { $where[] = qsprintf( $conn, 'm.name IN (%Ls)', $this->names); } switch ($this->status) { case self::STATUS_ACTIVE: $where[] = qsprintf( $conn, 'm.isDisabled = 0'); break; case self::STATUS_DISABLED: $where[] = qsprintf( $conn, 'm.isDisabled = 1'); break; case self::STATUS_ANY: break; default: throw new Exception("Unknown status '{$this->status}'!"); } if ($this->dateCreatedAfter) { $where[] = qsprintf( $conn, 'm.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore) { $where[] = qsprintf( $conn, 'm.dateCreated <= %d', $this->dateCreatedBefore); } if ($this->flagColor != '-1' && $this->flagColor !== null) { $flags = id(new PhabricatorFlagQuery()) ->withOwnerPHIDs(array($this->getViewer()->getPHID())) ->withTypes(array(PhabricatorMacroPHIDTypeMacro::TYPECONST)) ->withColors(array($this->flagColor)) ->setViewer($this->getViewer()) ->execute(); if (empty($flags)) { throw new PhabricatorEmptyQueryException('No matching flags.'); } else { $where[] = qsprintf( $conn, 'm.phid IN (%Ls)', mpull($flags, 'getObjectPHID')); } } $where[] = $this->buildPagingClause($conn); return $this->formatWhereClause($where); } protected function didFilterPage(array $macros) { $file_phids = mpull($macros, 'getFilePHID'); $files = id(new PhabricatorFileQuery()) ->setViewer($this->getViewer()) ->setParentQuery($this) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); foreach ($macros as $key => $macro) { $file = idx($files, $macro->getFilePHID()); if (!$file) { unset($macros[$key]); continue; } $macro->attachFile($file); } return $macros; } protected function getPagingColumn() { return 'm.id'; } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationMacro'; + } + } diff --git a/src/applications/mailinglists/phid/PhabricatorMailingListPHIDTypeList.php b/src/applications/mailinglists/phid/PhabricatorMailingListPHIDTypeList.php index eee01d277a..ccd14ae2e8 100644 --- a/src/applications/mailinglists/phid/PhabricatorMailingListPHIDTypeList.php +++ b/src/applications/mailinglists/phid/PhabricatorMailingListPHIDTypeList.php @@ -1,47 +1,44 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $list = $objects[$phid]; $handle->setName($list->getName()); $handle->setURI($list->getURI()); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/mailinglists/query/PhabricatorMailingListQuery.php b/src/applications/mailinglists/query/PhabricatorMailingListQuery.php index a7f98a4aa9..77551d3984 100644 --- a/src/applications/mailinglists/query/PhabricatorMailingListQuery.php +++ b/src/applications/mailinglists/query/PhabricatorMailingListQuery.php @@ -1,56 +1,60 @@ ids = $ids; return $this; } public function withPHIDs($phids) { $this->phids = $phids; return $this; } public function loadPage() { $table = new PhabricatorMetaMTAMailingList(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationMailingLists'; + } + } diff --git a/src/applications/maniphest/phid/ManiphestPHIDTypeTask.php b/src/applications/maniphest/phid/ManiphestPHIDTypeTask.php index dd076bc511..2af8135438 100644 --- a/src/applications/maniphest/phid/ManiphestPHIDTypeTask.php +++ b/src/applications/maniphest/phid/ManiphestPHIDTypeTask.php @@ -1,79 +1,76 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $task = $objects[$phid]; $id = $task->getID(); $title = $task->getTitle(); $handle->setName("T{$id}"); $handle->setFullName("T{$id}: {$title}"); $handle->setURI("/T{$id}"); if ($task->getStatus() != ManiphestTaskStatus::STATUS_OPEN) { $handle->setStatus(PhabricatorObjectHandleStatus::STATUS_CLOSED); } } } public function canLoadNamedObject($name) { return preg_match('/^T\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new ManiphestTaskQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/maniphest/query/ManiphestTaskQuery.php b/src/applications/maniphest/query/ManiphestTaskQuery.php index ccda6c0122..82aebd05fb 100644 --- a/src/applications/maniphest/query/ManiphestTaskQuery.php +++ b/src/applications/maniphest/query/ManiphestTaskQuery.php @@ -1,889 +1,894 @@ authorPHIDs = $authors; return $this; } public function withIDs(array $ids) { $this->taskIDs = $ids; return $this; } public function withPHIDs(array $phids) { $this->taskPHIDs = $phids; return $this; } public function withOwners(array $owners) { $this->includeUnowned = false; foreach ($owners as $k => $phid) { if ($phid == ManiphestTaskOwner::OWNER_UP_FOR_GRABS || $phid === null) { $this->includeUnowned = true; unset($owners[$k]); break; } } $this->ownerPHIDs = $owners; return $this; } public function withAllProjects(array $projects) { $this->includeNoProject = false; foreach ($projects as $k => $phid) { if ($phid == ManiphestTaskOwner::PROJECT_NO_PROJECT) { $this->includeNoProject = true; unset($projects[$k]); } } $this->projectPHIDs = $projects; return $this; } public function withoutProjects(array $projects) { $this->xprojectPHIDs = $projects; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withStatuses(array $statuses) { $this->statuses = $statuses; return $this; } public function withPriorities(array $priorities) { $this->priorities = $priorities; return $this; } public function withSubscribers(array $subscribers) { $this->subscriberPHIDs = $subscribers; return $this; } public function withFullTextSearch($fulltext_search) { $this->fullTextSearch = $fulltext_search; return $this; } public function setGroupBy($group) { $this->groupBy = $group; return $this; } public function setOrderBy($order) { $this->orderBy = $order; return $this; } public function withAnyProjects(array $projects) { $this->anyProjectPHIDs = $projects; return $this; } public function withAnyUserProjects(array $users) { $this->anyUserProjectPHIDs = $users; return $this; } public function withDateCreatedBefore($date_created_before) { $this->dateCreatedBefore = $date_created_before; return $this; } public function withDateCreatedAfter($date_created_after) { $this->dateCreatedAfter = $date_created_after; return $this; } public function loadPage() { // TODO: (T603) It is possible for a user to find the PHID of a project // they can't see, then query for tasks in that project and deduce the // identity of unknown/invisible projects. Before we allow the user to // execute a project-based PHID query, we should verify that they // can see the project. $task_dao = new ManiphestTask(); $conn = $task_dao->establishConnection('r'); $where = array(); $where[] = $this->buildTaskIDsWhereClause($conn); $where[] = $this->buildTaskPHIDsWhereClause($conn); $where[] = $this->buildStatusWhereClause($conn); $where[] = $this->buildStatusesWhereClause($conn); $where[] = $this->buildPrioritiesWhereClause($conn); $where[] = $this->buildAuthorWhereClause($conn); $where[] = $this->buildOwnerWhereClause($conn); $where[] = $this->buildSubscriberWhereClause($conn); $where[] = $this->buildProjectWhereClause($conn); $where[] = $this->buildAnyProjectWhereClause($conn); $where[] = $this->buildAnyUserProjectWhereClause($conn); $where[] = $this->buildXProjectWhereClause($conn); $where[] = $this->buildFullTextWhereClause($conn); if ($this->dateCreatedAfter) { $where[] = qsprintf( $conn, 'dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore) { $where[] = qsprintf( $conn, 'dateCreated <= %d', $this->dateCreatedBefore); } $where[] = $this->buildPagingClause($conn); $where = $this->formatWhereClause($where); $having = ''; $count = ''; if (count($this->projectPHIDs) > 1) { // We want to treat the query as an intersection query, not a union // query. We sum the project count and require it be the same as the // number of projects we're searching for. $count = ', COUNT(project.projectPHID) projectCount'; $having = qsprintf( $conn, 'HAVING projectCount = %d', count($this->projectPHIDs)); } $order = $this->buildCustomOrderClause($conn); // TODO: Clean up this nonstandardness. if (!$this->getLimit()) { $this->setLimit(self::DEFAULT_PAGE_SIZE); } $group_column = ''; switch ($this->groupBy) { case self::GROUP_PROJECT: $group_column = qsprintf( $conn, ', projectGroupName.indexedObjectPHID projectGroupPHID'); break; } $rows = queryfx_all( $conn, 'SELECT task.* %Q %Q FROM %T task %Q %Q %Q %Q %Q %Q', $count, $group_column, $task_dao->getTableName(), $this->buildJoinsClause($conn), $where, $this->buildGroupClause($conn), $having, $order, $this->buildLimitClause($conn)); switch ($this->groupBy) { case self::GROUP_PROJECT: $data = ipull($rows, null, 'id'); break; default: $data = $rows; break; } $tasks = $task_dao->loadAllFromArray($data); switch ($this->groupBy) { case self::GROUP_PROJECT: $results = array(); foreach ($rows as $row) { $task = clone $tasks[$row['id']]; $task->attachGroupByProjectPHID($row['projectGroupPHID']); $results[] = $task; } $tasks = $results; break; } return $tasks; } protected function willFilterPage(array $tasks) { if ($this->groupBy == self::GROUP_PROJECT) { // We should only return project groups which the user can actually see. $project_phids = mpull($tasks, 'getGroupByProjectPHID'); $projects = id(new PhabricatorProjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($project_phids) ->execute(); $projects = mpull($projects, null, 'getPHID'); foreach ($tasks as $key => $task) { if (empty($projects[$task->getGroupByProjectPHID()])) { unset($tasks[$key]); } } } return $tasks; } private function buildTaskIDsWhereClause(AphrontDatabaseConnection $conn) { if (!$this->taskIDs) { return null; } return qsprintf( $conn, 'id in (%Ld)', $this->taskIDs); } private function buildTaskPHIDsWhereClause(AphrontDatabaseConnection $conn) { if (!$this->taskPHIDs) { return null; } return qsprintf( $conn, 'phid in (%Ls)', $this->taskPHIDs); } private function buildStatusWhereClause(AphrontDatabaseConnection $conn) { static $map = array( self::STATUS_RESOLVED => ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, self::STATUS_WONTFIX => ManiphestTaskStatus::STATUS_CLOSED_WONTFIX, self::STATUS_INVALID => ManiphestTaskStatus::STATUS_CLOSED_INVALID, self::STATUS_SPITE => ManiphestTaskStatus::STATUS_CLOSED_SPITE, self::STATUS_DUPLICATE => ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE, ); switch ($this->status) { case self::STATUS_ANY: return null; case self::STATUS_OPEN: return 'status = 0'; case self::STATUS_CLOSED: return 'status > 0'; default: $constant = idx($map, $this->status); if (!$constant) { throw new Exception("Unknown status query '{$this->status}'!"); } return qsprintf( $conn, 'status = %d', $constant); } } private function buildStatusesWhereClause(AphrontDatabaseConnection $conn) { if ($this->statuses) { return qsprintf( $conn, 'status IN (%Ld)', $this->statuses); } return null; } private function buildPrioritiesWhereClause(AphrontDatabaseConnection $conn) { if ($this->priorities) { return qsprintf( $conn, 'priority IN (%Ld)', $this->priorities); } return null; } private function buildAuthorWhereClause(AphrontDatabaseConnection $conn) { if (!$this->authorPHIDs) { return null; } return qsprintf( $conn, 'authorPHID in (%Ls)', $this->authorPHIDs); } private function buildOwnerWhereClause(AphrontDatabaseConnection $conn) { if (!$this->ownerPHIDs) { if ($this->includeUnowned === null) { return null; } else if ($this->includeUnowned) { return qsprintf( $conn, 'ownerPHID IS NULL'); } else { return qsprintf( $conn, 'ownerPHID IS NOT NULL'); } } if ($this->includeUnowned) { return qsprintf( $conn, 'ownerPHID IN (%Ls) OR ownerPHID IS NULL', $this->ownerPHIDs); } else { return qsprintf( $conn, 'ownerPHID IN (%Ls)', $this->ownerPHIDs); } } private function buildFullTextWhereClause(AphrontDatabaseConnection $conn) { if (!strlen($this->fullTextSearch)) { return null; } // In doing a fulltext search, we first find all the PHIDs that match the // fulltext search, and then use that to limit the rest of the search $fulltext_query = new PhabricatorSearchQuery(); $fulltext_query->setQuery($this->fullTextSearch); // NOTE: Setting this to something larger than 2^53 will raise errors in // ElasticSearch, and billions of results won't fit in memory anyway. $fulltext_query->setParameter('limit', 100000); $fulltext_query->setParameter('type', ManiphestPHIDTypeTask::TYPECONST); $engine = PhabricatorSearchEngineSelector::newSelector()->newEngine(); $fulltext_results = $engine->executeSearch($fulltext_query); if (empty($fulltext_results)) { $fulltext_results = array(null); } return qsprintf( $conn, 'phid IN (%Ls)', $fulltext_results); } private function buildSubscriberWhereClause(AphrontDatabaseConnection $conn) { if (!$this->subscriberPHIDs) { return null; } return qsprintf( $conn, 'subscriber.subscriberPHID IN (%Ls)', $this->subscriberPHIDs); } private function buildProjectWhereClause(AphrontDatabaseConnection $conn) { if (!$this->projectPHIDs && !$this->includeNoProject) { return null; } $parts = array(); if ($this->projectPHIDs) { $parts[] = qsprintf( $conn, 'project.projectPHID in (%Ls)', $this->projectPHIDs); } if ($this->includeNoProject) { $parts[] = qsprintf( $conn, 'project.projectPHID IS NULL'); } return '('.implode(') OR (', $parts).')'; } private function buildAnyProjectWhereClause(AphrontDatabaseConnection $conn) { if (!$this->anyProjectPHIDs) { return null; } return qsprintf( $conn, 'anyproject.projectPHID IN (%Ls)', $this->anyProjectPHIDs); } private function buildAnyUserProjectWhereClause( AphrontDatabaseConnection $conn) { if (!$this->anyUserProjectPHIDs) { return null; } $projects = id(new PhabricatorProjectQuery()) ->setViewer($this->getViewer()) ->withMemberPHIDs($this->anyUserProjectPHIDs) ->execute(); $any_user_project_phids = mpull($projects, 'getPHID'); if (!$any_user_project_phids) { throw new PhabricatorEmptyQueryException(); } return qsprintf( $conn, 'anyproject.projectPHID IN (%Ls)', $any_user_project_phids); } private function buildXProjectWhereClause(AphrontDatabaseConnection $conn) { if (!$this->xprojectPHIDs) { return null; } return qsprintf( $conn, 'xproject.projectPHID IS NULL'); } private function buildCustomOrderClause(AphrontDatabaseConnection $conn) { $order = array(); switch ($this->groupBy) { case self::GROUP_NONE: break; case self::GROUP_PRIORITY: $order[] = 'priority'; break; case self::GROUP_OWNER: $order[] = 'ownerOrdering'; break; case self::GROUP_STATUS: $order[] = 'status'; break; case self::GROUP_PROJECT: $order[] = ''; break; default: throw new Exception("Unknown group query '{$this->groupBy}'!"); } switch ($this->orderBy) { case self::ORDER_PRIORITY: $order[] = 'priority'; $order[] = 'subpriority'; $order[] = 'dateModified'; break; case self::ORDER_CREATED: $order[] = 'id'; break; case self::ORDER_MODIFIED: $order[] = 'dateModified'; break; case self::ORDER_TITLE: $order[] = 'title'; break; default: throw new Exception("Unknown order query '{$this->orderBy}'!"); } $order = array_unique($order); if (empty($order)) { return null; } $reverse = ($this->getBeforeID() xor $this->getReversePaging()); foreach ($order as $k => $column) { switch ($column) { case 'subpriority': case 'ownerOrdering': case 'title': if ($reverse) { $order[$k] = "task.{$column} DESC"; } else { $order[$k] = "task.{$column} ASC"; } break; case '': // Put "No Project" at the end of the list. if ($reverse) { $order[$k] = 'projectGroupName.indexedObjectName IS NULL DESC, '. 'projectGroupName.indexedObjectName DESC'; } else { $order[$k] = 'projectGroupName.indexedObjectName IS NULL ASC, '. 'projectGroupName.indexedObjectName ASC'; } break; default: if ($reverse) { $order[$k] = "task.{$column} ASC"; } else { $order[$k] = "task.{$column} DESC"; } break; } } return 'ORDER BY '.implode(', ', $order); } private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { $project_dao = new ManiphestTaskProject(); $joins = array(); if ($this->projectPHIDs || $this->includeNoProject) { $joins[] = qsprintf( $conn_r, '%Q JOIN %T project ON project.taskPHID = task.phid', ($this->includeNoProject ? 'LEFT' : ''), $project_dao->getTableName()); } if ($this->anyProjectPHIDs || $this->anyUserProjectPHIDs) { $joins[] = qsprintf( $conn_r, 'JOIN %T anyproject ON anyproject.taskPHID = task.phid', $project_dao->getTableName()); } if ($this->xprojectPHIDs) { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T xproject ON xproject.taskPHID = task.phid AND xproject.projectPHID IN (%Ls)', $project_dao->getTableName(), $this->xprojectPHIDs); } if ($this->subscriberPHIDs) { $subscriber_dao = new ManiphestTaskSubscriber(); $joins[] = qsprintf( $conn_r, 'JOIN %T subscriber ON subscriber.taskPHID = task.phid', $subscriber_dao->getTableName()); } switch ($this->groupBy) { case self::GROUP_PROJECT: $ignore_group_phids = $this->getIgnoreGroupedProjectPHIDs(); if ($ignore_group_phids) { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T projectGroup ON task.phid = projectGroup.taskPHID AND projectGroup.projectPHID NOT IN (%Ls)', $project_dao->getTableName(), $ignore_group_phids); } else { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T projectGroup ON task.phid = projectGroup.taskPHID', $project_dao->getTableName()); } $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T projectGroupName ON projectGroup.projectPHID = projectGroupName.indexedObjectPHID', id(new ManiphestNameIndex())->getTableName()); break; } $joins[] = $this->buildApplicationSearchJoinClause($conn_r); return implode(' ', $joins); } private function buildGroupClause(AphrontDatabaseConnection $conn_r) { $joined_multiple_rows = (count($this->projectPHIDs) > 1) || (count($this->anyProjectPHIDs) > 1) || ($this->getApplicationSearchMayJoinMultipleRows()); $joined_project_name = ($this->groupBy == self::GROUP_PROJECT); // If we're joining multiple rows, we need to group the results by the // task IDs. if ($joined_multiple_rows) { if ($joined_project_name) { return 'GROUP BY task.phid, projectGroup.projectPHID'; } else { return 'GROUP BY task.phid'; } } else { return ''; } } /** * Return project PHIDs which we should ignore when grouping tasks by * project. For example, if a user issues a query like: * * Tasks in all projects: Frontend, Bugs * * ...then we don't show "Frontend" or "Bugs" groups in the result set, since * they're meaningless as all results are in both groups. * * Similarly, for queries like: * * Tasks in any projects: Public Relations * * ...we ignore the single project, as every result is in that project. (In * the case that there are several "any" projects, we do not ignore them.) * * @return list Project PHIDs which should be ignored in query * construction. */ private function getIgnoreGroupedProjectPHIDs() { $phids = array(); if ($this->projectPHIDs) { $phids[] = $this->projectPHIDs; } if (count($this->anyProjectPHIDs) == 1) { $phids[] = $this->anyProjectPHIDs; } // Maybe we should also exclude the "excludeProjectPHIDs"? It won't // impact the results, but we might end up with a better query plan. // Investigate this on real data? This is likely very rare. return array_mergev($phids); } private function loadCursorObject($id) { $results = id(new ManiphestTaskQuery()) ->setViewer($this->getPagingViewer()) ->withIDs(array((int)$id)) ->execute(); return head($results); } protected function getPagingValue($result) { $id = $result->getID(); switch ($this->groupBy) { case self::GROUP_NONE: return $id; case self::GROUP_PRIORITY: return $id.'.'.$result->getPriority(); case self::GROUP_OWNER: return rtrim($id.'.'.$result->getOwnerPHID(), '.'); case self::GROUP_STATUS: return $id.'.'.$result->getStatus(); case self::GROUP_PROJECT: return rtrim($id.'.'.$result->getGroupByProjectPHID(), '.'); default: throw new Exception("Unknown group query '{$this->groupBy}'!"); } } protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { $default = parent::buildPagingClause($conn_r); $before_id = $this->getBeforeID(); $after_id = $this->getAfterID(); if (!$before_id && !$after_id) { return $default; } $cursor_id = nonempty($before_id, $after_id); $cursor_parts = explode('.', $cursor_id, 2); $task_id = $cursor_parts[0]; $group_id = idx($cursor_parts, 1); $cursor = $this->loadCursorObject($task_id); if (!$cursor) { return null; } $columns = array(); switch ($this->groupBy) { case self::GROUP_NONE: break; case self::GROUP_PRIORITY: $columns[] = array( 'name' => 'task.priority', 'value' => (int)$group_id, 'type' => 'int', ); break; case self::GROUP_OWNER: $columns[] = array( 'name' => '(task.ownerOrdering IS NULL)', 'value' => (int)(strlen($group_id) ? 0 : 1), 'type' => 'int', ); if ($group_id) { $paging_users = id(new PhabricatorPeopleQuery()) ->setViewer($this->getViewer()) ->withPHIDs(array($group_id)) ->execute(); if (!$paging_users) { return null; } $columns[] = array( 'name' => 'task.ownerOrdering', 'value' => head($paging_users)->getUsername(), 'type' => 'string', 'reverse' => true, ); } break; case self::GROUP_STATUS: $columns[] = array( 'name' => 'task.status', 'value' => (int)$group_id, 'type' => 'int', ); break; case self::GROUP_PROJECT: $columns[] = array( 'name' => '(projectGroupName.indexedObjectName IS NULL)', 'value' => (int)(strlen($group_id) ? 0 : 1), 'type' => 'int', ); if ($group_id) { $paging_projects = id(new PhabricatorProjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs(array($group_id)) ->execute(); if (!$paging_projects) { return null; } $columns[] = array( 'name' => 'projectGroupName.indexedObjectName', 'value' => head($paging_projects)->getName(), 'type' => 'string', 'reverse' => true, ); } break; default: throw new Exception("Unknown group query '{$this->groupBy}'!"); } switch ($this->orderBy) { case self::ORDER_PRIORITY: if ($this->groupBy != self::GROUP_PRIORITY) { $columns[] = array( 'name' => 'task.priority', 'value' => (int)$cursor->getPriority(), 'type' => 'int', ); } $columns[] = array( 'name' => 'task.subpriority', 'value' => (int)$cursor->getSubpriority(), 'type' => 'int', 'reverse' => true, ); $columns[] = array( 'name' => 'task.dateModified', 'value' => (int)$cursor->getDateModified(), 'type' => 'int', ); break; case self::ORDER_CREATED: $columns[] = array( 'name' => 'task.id', 'value' => (int)$cursor->getID(), 'type' => 'int', ); break; case self::ORDER_MODIFIED: $columns[] = array( 'name' => 'task.dateModified', 'value' => (int)$cursor->getDateModified(), 'type' => 'int', ); break; case self::ORDER_TITLE: $columns[] = array( 'name' => 'task.title', 'value' => $cursor->getTitle(), 'type' => 'string', ); $columns[] = array( 'name' => 'task.id', 'value' => $cursor->getID(), 'type' => 'int', ); break; default: throw new Exception("Unknown order query '{$this->orderBy}'!"); } return $this->buildPagingClauseFromMultipleColumns( $conn_r, $columns, array( 'reversed' => (bool)($before_id xor $this->getReversePaging()), )); } protected function getApplicationSearchObjectPHIDColumn() { return 'task.phid'; } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationManiphest'; + } + } diff --git a/src/applications/meta/phid/PhabricatorApplicationPHIDTypeApplication.php b/src/applications/meta/phid/PhabricatorApplicationPHIDTypeApplication.php index 6554bd717c..718557266f 100644 --- a/src/applications/meta/phid/PhabricatorApplicationPHIDTypeApplication.php +++ b/src/applications/meta/phid/PhabricatorApplicationPHIDTypeApplication.php @@ -1,48 +1,45 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $application = $objects[$phid]; $handle->setName($application->getName()); $handle->setURI($application->getApplicationURI()); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/meta/query/PhabricatorApplicationQuery.php b/src/applications/meta/query/PhabricatorApplicationQuery.php index 30106cd1f2..ca14d6954a 100644 --- a/src/applications/meta/query/PhabricatorApplicationQuery.php +++ b/src/applications/meta/query/PhabricatorApplicationQuery.php @@ -1,135 +1,144 @@ nameContains = $name_contains; return $this; } public function withInstalled($installed) { $this->installed = $installed; return $this; } public function withBeta($beta) { $this->beta = $beta; return $this; } public function withFirstParty($first_party) { $this->firstParty = $first_party; return $this; } public function withUnlisted($unlisted) { $this->unlisted = $unlisted; return $this; } public function withClasses(array $classes) { $this->classes = $classes; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function setOrder($order) { $this->order = $order; return $this; } public function loadPage() { $apps = PhabricatorApplication::getAllApplications(); if ($this->classes) { $classes = array_fuse($this->classes); foreach ($apps as $key => $app) { if (empty($classes[get_class($app)])) { unset($apps[$key]); } } } if ($this->phids) { $phids = array_fuse($this->phids); foreach ($apps as $key => $app) { if (empty($phids[$app->getPHID()])) { unset($apps[$key]); } } } if (strlen($this->nameContains)) { foreach ($apps as $key => $app) { if (stripos($app->getName(), $this->nameContains) === false) { unset($apps[$key]); } } } if ($this->installed !== null) { foreach ($apps as $key => $app) { if ($app->isInstalled() != $this->installed) { unset($apps[$key]); } } } if ($this->beta !== null) { foreach ($apps as $key => $app) { if ($app->isBeta() != $this->beta) { unset($apps[$key]); } } } if ($this->firstParty !== null) { foreach ($apps as $key => $app) { if ($app->isFirstParty() != $this->firstParty) { unset($apps[$key]); } } } if ($this->unlisted !== null) { foreach ($apps as $key => $app) { if ($app->isUnlisted() != $this->unlisted) { unset($apps[$key]); } } } switch ($this->order) { case self::ORDER_NAME: $apps = msort($apps, 'getName'); break; case self::ORDER_APPLICATION: $apps = $apps; break; default: throw new Exception( pht('Unknown order "%s"!', $this->order)); } return $apps; } + + public function getQueryApplicationClass() { + // NOTE: Although this belongs to the "Applications" application, trying + // to filter its results just leaves us recursing indefinitely. Users + // always have access to applications regardless of other policy settings + // anyway. + return null; + } + } diff --git a/src/applications/notification/PhabricatorNotificationQuery.php b/src/applications/notification/PhabricatorNotificationQuery.php index 736990e08e..731d9dca73 100644 --- a/src/applications/notification/PhabricatorNotificationQuery.php +++ b/src/applications/notification/PhabricatorNotificationQuery.php @@ -1,114 +1,120 @@ userPHID = $user_phid; return $this; } public function withKeys(array $keys) { $this->keys = $keys; return $this; } /** * Filter results by read/unread status. Note that `true` means to return * only unread notifications, while `false` means to return only //read// * notifications. The default is `null`, which returns both. * * @param mixed True or false to filter results by read status. Null to remove * the filter. * @return this * @task config */ public function withUnread($unread) { $this->unread = $unread; return $this; } /* -( Query Execution )---------------------------------------------------- */ protected function loadPage() { if (!$this->userPHID) { throw new Exception("Call setUser() before executing the query"); } $story_table = new PhabricatorFeedStoryData(); $notification_table = new PhabricatorFeedStoryNotification(); $conn = $story_table->establishConnection('r'); $data = queryfx_all( $conn, "SELECT story.*, notif.hasViewed FROM %T notif JOIN %T story ON notif.chronologicalKey = story.chronologicalKey %Q ORDER BY notif.chronologicalKey DESC %Q", $notification_table->getTableName(), $story_table->getTableName(), $this->buildWhereClause($conn), $this->buildLimitClause($conn)); $viewed_map = ipull($data, 'hasViewed', 'chronologicalKey'); $stories = PhabricatorFeedStory::loadAllFromRows( $data, $this->getViewer()); foreach ($stories as $key => $story) { $story->setHasViewed($viewed_map[$key]); } return $stories; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->userPHID) { $where[] = qsprintf( $conn_r, 'notif.userPHID = %s', $this->userPHID); } if ($this->unread !== null) { $where[] = qsprintf( $conn_r, 'notif.hasViewed = %d', (int)!$this->unread); } if ($this->keys) { $where[] = qsprintf( $conn_r, 'notif.chronologicalKey IN (%Ls)', $this->keys); } return $this->formatWhereClause($where); } protected function getPagingValue($item) { return $item->getChronologicalKey(); } + + public function getQueryApplicationClass() { + // TODO: No actual "Notification" app yet, but there probably should be. + return null; + } + } diff --git a/src/applications/owners/phid/PhabricatorOwnersPHIDTypePackage.php b/src/applications/owners/phid/PhabricatorOwnersPHIDTypePackage.php index af7976aad0..b82b47a9ea 100644 --- a/src/applications/owners/phid/PhabricatorOwnersPHIDTypePackage.php +++ b/src/applications/owners/phid/PhabricatorOwnersPHIDTypePackage.php @@ -1,46 +1,43 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $package = $objects[$phid]; $name = $package->getName(); $id = $package->getID(); $handle->setName($name); $handle->setURI("/owners/package/{$id}/"); } } } diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index 68b0dfe0ed..90996c9f04 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -1,82 +1,87 @@ ownerPHIDs = $phids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } protected function loadPage() { $table = new PhabricatorOwnersPackage(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT p.* FROM %T p %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } private function buildJoinClause(AphrontDatabaseConnection $conn_r) { $joins = array(); if ($this->ownerPHIDs) { $joins[] = qsprintf( $conn_r, 'JOIN %T o ON o.packageID = p.id', id(new PhabricatorOwnersOwner())->getTableName()); } return implode(' ', $joins); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->phids) { $where[] = qsprintf( $conn_r, 'p.phid IN (%Ls)', $this->phids); } if ($this->ownerPHIDs) { $base_phids = $this->ownerPHIDs; $query = new PhabricatorProjectQuery(); $query->setViewer($this->getViewer()); $query->withMemberPHIDs($base_phids); $projects = $query->execute(); $project_phids = mpull($projects, 'getPHID'); $all_phids = array_merge($base_phids, $project_phids); $where[] = qsprintf( $conn_r, 'o.userPHID IN (%Ls)', $all_phids); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationOwners'; + } + } diff --git a/src/applications/paste/phid/PhabricatorPastePHIDTypePaste.php b/src/applications/paste/phid/PhabricatorPastePHIDTypePaste.php index 024cdd9f16..5e3ef4fd81 100644 --- a/src/applications/paste/phid/PhabricatorPastePHIDTypePaste.php +++ b/src/applications/paste/phid/PhabricatorPastePHIDTypePaste.php @@ -1,76 +1,73 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $paste = $objects[$phid]; $id = $paste->getID(); $name = $paste->getFullName(); $handle->setName("P{$id}"); $handle->setFullName($name); $handle->setURI("/P{$id}"); } } public function canLoadNamedObject($name) { return preg_match('/^P\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new PhabricatorPasteQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/paste/query/PhabricatorPasteQuery.php b/src/applications/paste/query/PhabricatorPasteQuery.php index aeea224205..c4db19e1ff 100644 --- a/src/applications/paste/query/PhabricatorPasteQuery.php +++ b/src/applications/paste/query/PhabricatorPasteQuery.php @@ -1,252 +1,257 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $phids) { $this->authorPHIDs = $phids; return $this; } public function withParentPHIDs(array $phids) { $this->parentPHIDs = $phids; return $this; } public function needContent($need_content) { $this->needContent = $need_content; return $this; } public function needRawContent($need_raw_content) { $this->needRawContent = $need_raw_content; return $this; } public function withLanguages(array $languages) { $this->includeNoLanguage = false; foreach ($languages as $key => $language) { if ($language === null) { $languages[$key] = ''; continue; } } $this->languages = $languages; return $this; } public function withDateCreatedBefore($date_created_before) { $this->dateCreatedBefore = $date_created_before; return $this; } public function withDateCreatedAfter($date_created_after) { $this->dateCreatedAfter = $date_created_after; return $this; } protected function loadPage() { $table = new PhabricatorPaste(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT paste.* FROM %T paste %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $pastes = $table->loadAllFromArray($data); return $pastes; } protected function didFilterPage(array $pastes) { if ($this->needRawContent) { $pastes = $this->loadRawContent($pastes); } if ($this->needContent) { $pastes = $this->loadContent($pastes); } return $pastes; } protected function buildWhereClause($conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->parentPHIDs) { $where[] = qsprintf( $conn_r, 'parentPHID IN (%Ls)', $this->parentPHIDs); } if ($this->languages) { $where[] = qsprintf( $conn_r, 'language IN (%Ls)', $this->languages); } if ($this->dateCreatedAfter) { $where[] = qsprintf( $conn_r, 'dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore) { $where[] = qsprintf( $conn_r, 'dateCreated <= %d', $this->dateCreatedBefore); } return $this->formatWhereClause($where); } private function getContentCacheKey(PhabricatorPaste $paste) { return 'P'.$paste->getID().':content/'.$paste->getLanguage(); } private function loadRawContent(array $pastes) { $file_phids = mpull($pastes, 'getFilePHID'); $files = id(new PhabricatorFileQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); foreach ($pastes as $key => $paste) { $file = idx($files, $paste->getFilePHID()); if (!$file) { unset($pastes[$key]); continue; } try { $paste->attachRawContent($file->loadFileData()); } catch (Exception $ex) { // We can hit various sorts of file storage issues here. Just drop the // paste if the file is dead. unset($pastes[$key]); continue; } } return $pastes; } private function loadContent(array $pastes) { $cache = new PhabricatorKeyValueDatabaseCache(); $cache = new PhutilKeyValueCacheProfiler($cache); $cache->setProfiler(PhutilServiceProfiler::getInstance()); $keys = array(); foreach ($pastes as $paste) { $keys[] = $this->getContentCacheKey($paste); } $caches = $cache->getKeys($keys); $results = array(); $need_raw = array(); foreach ($pastes as $key => $paste) { $key = $this->getContentCacheKey($paste); if (isset($caches[$key])) { $paste->attachContent(phutil_safe_html($caches[$key])); $results[$paste->getID()] = $paste; } else { $need_raw[$key] = $paste; } } if (!$need_raw) { return $results; } $write_data = array(); $need_raw = $this->loadRawContent($need_raw); foreach ($need_raw as $key => $paste) { $content = $this->buildContent($paste); $paste->attachContent($content); $write_data[$this->getContentCacheKey($paste)] = (string)$content; $results[$paste->getID()] = $paste; } $cache->setKeys($write_data); return $results; } private function buildContent(PhabricatorPaste $paste) { $language = $paste->getLanguage(); $source = $paste->getRawContent(); if (empty($language)) { return PhabricatorSyntaxHighlighter::highlightWithFilename( $paste->getTitle(), $source); } else { return PhabricatorSyntaxHighlighter::highlightWithLanguage( $language, $source); } } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPaste'; + } + } diff --git a/src/applications/people/phid/PhabricatorPeoplePHIDTypeExternal.php b/src/applications/people/phid/PhabricatorPeoplePHIDTypeExternal.php index b3c0044e60..d4e8241c19 100644 --- a/src/applications/people/phid/PhabricatorPeoplePHIDTypeExternal.php +++ b/src/applications/people/phid/PhabricatorPeoplePHIDTypeExternal.php @@ -1,47 +1,44 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $account = $objects[$phid]; $display_name = $account->getDisplayName(); $handle->setName($display_name); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/people/phid/PhabricatorPeoplePHIDTypeUser.php b/src/applications/people/phid/PhabricatorPeoplePHIDTypeUser.php index 4cf6e21f30..a0bce6ea47 100644 --- a/src/applications/people/phid/PhabricatorPeoplePHIDTypeUser.php +++ b/src/applications/people/phid/PhabricatorPeoplePHIDTypeUser.php @@ -1,54 +1,51 @@ setViewer($query->getViewer()) - ->setParentQuery($query) ->withPHIDs($phids) ->needProfileImage(true) - ->needStatus(true) - ->execute(); + ->needStatus(true); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $user = $objects[$phid]; $handle->setName($user->getUsername()); $handle->setURI('/p/'.$user->getUsername().'/'); $handle->setFullName( $user->getUsername().' ('.$user->getRealName().')'); $handle->setImageURI($user->loadProfileImageURI()); $handle->setDisabled($user->getIsDisabled()); if ($user->hasStatus()) { $status = $user->getStatus(); $handle->setStatus($status->getTextStatus()); $handle->setTitle($status->getTerseSummary($query->getViewer())); } } } } diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index 39613c5011..f2c0b312bd 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -1,285 +1,289 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withEmails(array $emails) { $this->emails = $emails; return $this; } public function withRealnames(array $realnames) { $this->realnames = $realnames; return $this; } public function withUsernames(array $usernames) { $this->usernames = $usernames; return $this; } public function withDateCreatedBefore($date_created_before) { $this->dateCreatedBefore = $date_created_before; return $this; } public function withDateCreatedAfter($date_created_after) { $this->dateCreatedAfter = $date_created_after; return $this; } public function withIsAdmin($admin) { $this->isAdmin = $admin; return $this; } public function withIsSystemAgent($system_agent) { $this->isSystemAgent = $system_agent; return $this; } public function withIsDisabled($disabled) { $this->isDisabled = $disabled; return $this; } public function withNameLike($like) { $this->nameLike = $like; return $this; } public function needPrimaryEmail($need) { $this->needPrimaryEmail = $need; return $this; } public function needProfile($need) { $this->needProfile = $need; return $this; } public function needProfileImage($need) { $this->needProfileImage = $need; return $this; } public function needStatus($need) { $this->needStatus = $need; return $this; } public function loadPage() { $table = new PhabricatorUser(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T user %Q %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinsClause($conn_r), $this->buildWhereClause($conn_r), $this->buildApplicationSearchGroupClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); if ($this->needPrimaryEmail) { $table->putInSet(new LiskDAOSet()); } return $table->loadAllFromArray($data); } protected function didFilterPage(array $users) { if ($this->needProfile) { $user_list = mpull($users, null, 'getPHID'); $profiles = new PhabricatorUserProfile(); $profiles = $profiles->loadAllWhere('userPHID IN (%Ls)', array_keys($user_list)); $profiles = mpull($profiles, null, 'getUserPHID'); foreach ($user_list as $user_phid => $user) { $profile = idx($profiles, $user_phid); if (!$profile) { $profile = new PhabricatorUserProfile(); $profile->setUserPHID($user_phid); } $user->attachUserProfile($profile); } } if ($this->needProfileImage) { $user_profile_file_phids = mpull($users, 'getProfileImagePHID'); $user_profile_file_phids = array_filter($user_profile_file_phids); if ($user_profile_file_phids) { $files = id(new PhabricatorFileQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($user_profile_file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); } else { $files = array(); } foreach ($users as $user) { $image_phid = $user->getProfileImagePHID(); if (isset($files[$image_phid])) { $profile_image_uri = $files[$image_phid]->getBestURI(); } else { $profile_image_uri = PhabricatorUser::getDefaultProfileImageURI(); } $user->attachProfileImageURI($profile_image_uri); } } if ($this->needStatus) { $user_list = mpull($users, null, 'getPHID'); $statuses = id(new PhabricatorUserStatus())->loadCurrentStatuses( array_keys($user_list)); foreach ($user_list as $phid => $user) { $status = idx($statuses, $phid); if ($status) { $user->attachStatus($status); } } } return $users; } private function buildJoinsClause($conn_r) { $joins = array(); if ($this->emails) { $email_table = new PhabricatorUserEmail(); $joins[] = qsprintf( $conn_r, 'JOIN %T email ON email.userPHID = user.PHID', $email_table->getTableName()); } $joins[] = $this->buildApplicationSearchJoinClause($conn_r); $joins = implode(' ', $joins); return $joins; } private function buildWhereClause($conn_r) { $where = array(); if ($this->usernames) { $where[] = qsprintf( $conn_r, 'user.userName IN (%Ls)', $this->usernames); } if ($this->emails) { $where[] = qsprintf( $conn_r, 'email.address IN (%Ls)', $this->emails); } if ($this->realnames) { $where[] = qsprintf( $conn_r, 'user.realName IN (%Ls)', $this->realnames); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'user.phid IN (%Ls)', $this->phids); } if ($this->ids) { $where[] = qsprintf( $conn_r, 'user.id IN (%Ld)', $this->ids); } if ($this->dateCreatedAfter) { $where[] = qsprintf( $conn_r, 'user.dateCreated >= %d', $this->dateCreatedAfter); } if ($this->dateCreatedBefore) { $where[] = qsprintf( $conn_r, 'user.dateCreated <= %d', $this->dateCreatedBefore); } if ($this->isAdmin) { $where[] = qsprintf( $conn_r, 'user.isAdmin = 1'); } if ($this->isDisabled) { $where[] = qsprintf( $conn_r, 'user.isDisabled = 1'); } if ($this->isSystemAgent) { $where[] = qsprintf( $conn_r, 'user.isSystemAgent = 1'); } if (strlen($this->nameLike)) { $where[] = qsprintf( $conn_r, 'user.username LIKE %~ OR user.realname LIKE %~', $this->nameLike, $this->nameLike); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } protected function getPagingColumn() { return 'user.id'; } protected function getApplicationSearchObjectPHIDColumn() { return 'user.phid'; } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPeople'; + } + } diff --git a/src/applications/phame/phid/PhabricatorPhamePHIDTypeBlog.php b/src/applications/phame/phid/PhabricatorPhamePHIDTypeBlog.php index fe24e757b5..bf20c112e0 100644 --- a/src/applications/phame/phid/PhabricatorPhamePHIDTypeBlog.php +++ b/src/applications/phame/phid/PhabricatorPhamePHIDTypeBlog.php @@ -1,46 +1,43 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $blog = $objects[$phid]; $handle->setName($blog->getName()); $handle->setFullName($blog->getName()); $handle->setURI('/phame/blog/view/'.$blog->getID().'/'); } } } diff --git a/src/applications/phame/phid/PhabricatorPhamePHIDTypePost.php b/src/applications/phame/phid/PhabricatorPhamePHIDTypePost.php index 790b1084f6..32ecbc231c 100644 --- a/src/applications/phame/phid/PhabricatorPhamePHIDTypePost.php +++ b/src/applications/phame/phid/PhabricatorPhamePHIDTypePost.php @@ -1,46 +1,43 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $post = $objects[$phid]; $handle->setName($post->getTitle()); $handle->setFullName($post->getTitle()); $handle->setURI('/phame/post/view/'.$post->getID().'/'); } } } diff --git a/src/applications/phame/query/PhameBlogQuery.php b/src/applications/phame/query/PhameBlogQuery.php index 022e694275..eb253de6f5 100644 --- a/src/applications/phame/query/PhameBlogQuery.php +++ b/src/applications/phame/query/PhameBlogQuery.php @@ -1,78 +1,83 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withDomain($domain) { $this->domain = $domain; return $this; } protected function loadPage() { $table = new PhameBlog(); $conn_r = $table->establishConnection('r'); $where_clause = $this->buildWhereClause($conn_r); $order_clause = $this->buildOrderClause($conn_r); $limit_clause = $this->buildLimitClause($conn_r); $data = queryfx_all( $conn_r, 'SELECT * FROM %T b %Q %Q %Q', $table->getTableName(), $where_clause, $order_clause, $limit_clause); $blogs = $table->loadAllFromArray($data); return $blogs; } private function buildWhereClause($conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ls)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->domain) { $where[] = qsprintf( $conn_r, 'domain = %s', $this->domain); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + // TODO: Can we set this without breaking public blogs? + return null; + } + } diff --git a/src/applications/phame/query/PhamePostQuery.php b/src/applications/phame/query/PhamePostQuery.php index 6356f54bec..94430990d4 100644 --- a/src/applications/phame/query/PhamePostQuery.php +++ b/src/applications/phame/query/PhamePostQuery.php @@ -1,144 +1,149 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withBloggerPHIDs(array $blogger_phids) { $this->bloggerPHIDs = $blogger_phids; return $this; } public function withBlogPHIDs(array $blog_phids) { $this->blogPHIDs = $blog_phids; return $this; } public function withPhameTitles(array $phame_titles) { $this->phameTitles = $phame_titles; return $this; } public function withVisibility($visibility) { $this->visibility = $visibility; return $this; } public function withPublishedAfter($time) { $this->publishedAfter = $time; return $this; } protected function loadPage() { $table = new PhamePost(); $conn_r = $table->establishConnection('r'); $where_clause = $this->buildWhereClause($conn_r); $order_clause = $this->buildOrderClause($conn_r); $limit_clause = $this->buildLimitClause($conn_r); $data = queryfx_all( $conn_r, 'SELECT * FROM %T p %Q %Q %Q', $table->getTableName(), $where_clause, $order_clause, $limit_clause); $posts = $table->loadAllFromArray($data); if ($posts) { // We require these to do visibility checks, so load them unconditionally. $blog_phids = mpull($posts, 'getBlogPHID'); $blogs = id(new PhameBlogQuery()) ->setViewer($this->getViewer()) ->withPHIDs($blog_phids) ->execute(); $blogs = mpull($blogs, null, 'getPHID'); foreach ($posts as $post) { if (isset($blogs[$post->getBlogPHID()])) { $post->setBlog($blogs[$post->getBlogPHID()]); } } } return $posts; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'p.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'p.phid IN (%Ls)', $this->phids); } if ($this->bloggerPHIDs) { $where[] = qsprintf( $conn_r, 'p.bloggerPHID IN (%Ls)', $this->bloggerPHIDs); } if ($this->phameTitles) { $where[] = qsprintf( $conn_r, 'p.phameTitle IN (%Ls)', $this->phameTitles); } if ($this->visibility !== null) { $where[] = qsprintf( $conn_r, 'p.visibility = %d', $this->visibility); } if ($this->publishedAfter !== null) { $where[] = qsprintf( $conn_r, 'p.datePublished > %d', $this->publishedAfter); } if ($this->blogPHIDs) { $where[] = qsprintf( $conn_r, 'p.blogPHID in (%Ls)', $this->blogPHIDs); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + // TODO: Does setting this break public blogs? + return null; + } + } diff --git a/src/applications/phid/query/PhabricatorHandleQuery.php b/src/applications/phid/query/PhabricatorHandleQuery.php index 5e089b1ed1..c3e923c0a7 100644 --- a/src/applications/phid/query/PhabricatorHandleQuery.php +++ b/src/applications/phid/query/PhabricatorHandleQuery.php @@ -1,70 +1,74 @@ phids = $phids; return $this; } public function loadPage() { $types = PhabricatorPHIDType::getAllTypes(); $phids = array_unique($this->phids); if (!$phids) { return array(); } $object_query = id(new PhabricatorObjectQuery()) ->withPHIDs($phids) ->setViewer($this->getViewer()); $objects = $object_query->execute(); $filtered = $object_query->getPolicyFilteredPHIDs(); $groups = array(); foreach ($phids as $phid) { $type = phid_get_type($phid); $groups[$type][] = $phid; } $results = array(); foreach ($groups as $type => $phid_group) { $handles = array(); foreach ($phid_group as $key => $phid) { if (isset($handles[$phid])) { unset($phid_group[$key]); // The input had a duplicate PHID; just skip it. continue; } $handles[$phid] = id(new PhabricatorObjectHandle()) ->setType($type) ->setPHID($phid); if (isset($objects[$phid])) { $handles[$phid]->setComplete(true); } else if (isset($filtered[$phid])) { $handles[$phid]->setPolicyFiltered(true); } } if (isset($types[$type])) { $type_objects = array_select_keys($objects, $phid_group); if ($type_objects) { $have_object_phids = array_keys($type_objects); $types[$type]->loadHandles( $this, array_select_keys($handles, $have_object_phids), $type_objects); } } $results += $handles; } return $results; } + public function getQueryApplicationClass() { + return null; + } + } diff --git a/src/applications/phid/query/PhabricatorObjectQuery.php b/src/applications/phid/query/PhabricatorObjectQuery.php index a6ffa02e7e..a786d4f72d 100644 --- a/src/applications/phid/query/PhabricatorObjectQuery.php +++ b/src/applications/phid/query/PhabricatorObjectQuery.php @@ -1,155 +1,159 @@ phids = $phids; return $this; } public function withNames(array $names) { $this->names = $names; return $this; } public function withTypes(array $types) { $this->types = $types; return $this; } public function loadPage() { if ($this->namedResults === null) { $this->namedResults = array(); } $types = PhabricatorPHIDType::getAllTypes(); if ($this->types) { $types = array_select_keys($types, $this->types); } $names = array_unique($this->names); $phids = $this->phids; // We allow objects to be named by their PHID in addition to their normal // name so that, e.g., CLI tools which accept object names can also accept // PHIDs and work as users expect. $actually_phids = array(); if ($names) { foreach ($names as $key => $name) { if (!strncmp($name, 'PHID-', 5)) { $actually_phids[] = $name; $phids[] = $name; unset($names[$key]); } } } $phids = array_unique($phids); if ($names) { $name_results = $this->loadObjectsByName($types, $names); } else { $name_results = array(); } if ($phids) { $phid_results = $this->loadObjectsByPHID($types, $phids); } else { $phid_results = array(); } foreach ($actually_phids as $phid) { if (isset($phid_results[$phid])) { $name_results[$phid] = $phid_results[$phid]; } } $this->namedResults += $name_results; return $phid_results + mpull($name_results, null, 'getPHID'); } public function getNamedResults() { if ($this->namedResults === null) { throw new Exception("Call execute() before getNamedResults()!"); } return $this->namedResults; } private function loadObjectsByName(array $types, array $names) { $groups = array(); foreach ($names as $name) { foreach ($types as $type => $type_impl) { if (!$type_impl->canLoadNamedObject($name)) { continue; } $groups[$type][] = $name; break; } } $results = array(); foreach ($groups as $type => $group) { $results += $types[$type]->loadNamedObjects($this, $group); } return $results; } private function loadObjectsByPHID(array $types, array $phids) { $results = array(); $workspace = $this->getObjectsFromWorkspace($phids); foreach ($phids as $key => $phid) { if (isset($workspace[$phid])) { $results[$phid] = $workspace[$phid]; unset($phids[$key]); } } if (!$phids) { return $results; } $groups = array(); foreach ($phids as $phid) { $type = phid_get_type($phid); $groups[$type][] = $phid; } foreach ($groups as $type => $group) { if (isset($types[$type])) { $objects = $types[$type]->loadObjects($this, $group); $results += mpull($objects, null, 'getPHID'); } } return $results; } protected function didFilterResults(array $filtered) { foreach ($this->namedResults as $name => $result) { if (isset($filtered[$result->getPHID()])) { unset($this->namedResults[$name]); } } } /** * This query disables policy filtering because it is performed in the * subqueries which actually load objects. We don't need to re-filter * results, since policies have already been applied. */ protected function shouldDisablePolicyFiltering() { return true; } + public function getQueryApplicationClass() { + return null; + } + } diff --git a/src/applications/phid/type/PhabricatorPHIDType.php b/src/applications/phid/type/PhabricatorPHIDType.php index 47e1351922..6ee4c4e8ec 100644 --- a/src/applications/phid/type/PhabricatorPHIDType.php +++ b/src/applications/phid/type/PhabricatorPHIDType.php @@ -1,89 +1,132 @@ PHIDs to load. + * @return PhabricatorPolicyAwareQuery Query object which loads the + * specified PHIDs when executed. + */ + abstract protected function buildQueryForObjects( PhabricatorObjectQuery $query, array $phids); + /** + * Load objects of this type, by PHID. For most PHID types, it is only + * necessary to implement @{method:buildQueryForObjects} to get object + * loading to work. + * + * @param PhabricatorObjectQuery Query being executed. + * @param list PHIDs to load. + * @return list Corresponding objects. + */ + public function loadObjects( + PhabricatorObjectQuery $query, + array $phids) { + + $object_query = $this->buildQueryForObjects($query, $phids) + ->setViewer($query->getViewer()) + ->setParentQuery($query); + + // If the user doesn't have permission to use the application at all, + // just mark all the PHIDs as filtered. This primarily makes these + // objects show up as "Restricted" instead of "Unknown" when loaded as + // handles, which is technically true. + if (!$object_query->canViewerUseQueryApplication()) { + $object_query->addPolicyFilteredPHIDs(array_fuse($phids)); + return array(); + } + + return $object_query->execute(); + } + + /** * Populate provided handles with application-specific data, like titles and * URIs. * * NOTE: The `$handles` and `$objects` lists are guaranteed to be nonempty * and have the same keys: subclasses are expected to load information only * for handles with visible objects. * * Because of this guarantee, a safe implementation will typically look like* * * foreach ($handles as $phid => $handle) { * $object = $objects[$phid]; * * $handle->setStuff($object->getStuff()); * // ... * } * * In general, an implementation should call `setName()` and `setURI()` on * each handle at a minimum. See @{class:PhabricatorObjectHandle} for other * handle properties. * * @param PhabricatorHandleQuery Issuing query object. * @param list Handles to populate with data. * @param list Objects for these PHIDs loaded by - * @{method:loadObjects()}. + * @{method:buildQueryForObjects()}. * @return void */ abstract public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects); public function canLoadNamedObject($name) { return false; } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { throw new Exception("Not implemented!"); } public static function getAllTypes() { static $types; if ($types === null) { $objects = id(new PhutilSymbolLoader()) ->setAncestorClass(__CLASS__) ->loadObjects(); $map = array(); $original = array(); foreach ($objects as $object) { $type = $object->getTypeConstant(); if (isset($map[$type])) { $that_class = $original[$type]; $this_class = get_class($object); throw new Exception( "Two PhabricatorPHIDType classes ({$that_class}, {$this_class}) ". "both handle PHID type '{$type}'. A type may be handled by only ". "one class."); } $original[$type] = get_class($object); $map[$type] = $object; } $types = $map; } return $types; } } diff --git a/src/applications/phlux/phid/PhluxPHIDTypeVariable.php b/src/applications/phlux/phid/PhluxPHIDTypeVariable.php index e6dc1a5f66..5be0c23778 100644 --- a/src/applications/phlux/phid/PhluxPHIDTypeVariable.php +++ b/src/applications/phlux/phid/PhluxPHIDTypeVariable.php @@ -1,50 +1,47 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $variable = $objects[$phid]; $key = $variable->getVariableKey(); $handle->setName($key); $handle->setFullName(pht('Variable "%s"', $key)); $handle->setURI("/phlux/view/{$key}/"); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/phlux/query/PhluxVariableQuery.php b/src/applications/phlux/query/PhluxVariableQuery.php index 89e61b87a0..3b9dbeb17e 100644 --- a/src/applications/phlux/query/PhluxVariableQuery.php +++ b/src/applications/phlux/query/PhluxVariableQuery.php @@ -1,68 +1,72 @@ phids = $phids; return $this; } public function withKeys(array $keys) { $this->keys = $keys; return $this; } protected function loadPage() { $table = new PhluxVariable(); $conn_r = $table->establishConnection('r'); $rows = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($rows); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->keys) { $where[] = qsprintf( $conn_r, 'variableKey IN (%Ls)', $this->keys); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } return $this->formatWhereClause($where); } protected function getPagingColumn() { return 'variableKey'; } protected function getPagingValue($result) { return $result->getVariableKey(); } protected function getReversePaging() { return true; } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPhlux'; + } + } diff --git a/src/applications/pholio/phid/PholioPHIDTypeImage.php b/src/applications/pholio/phid/PholioPHIDTypeImage.php index 87c5dadf48..54b806cc21 100644 --- a/src/applications/pholio/phid/PholioPHIDTypeImage.php +++ b/src/applications/pholio/phid/PholioPHIDTypeImage.php @@ -1,48 +1,45 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $image = $objects[$phid]; $id = $image->getID(); $mock_id = $image->getMockID(); $name = $image->getName(); $handle->setURI("/M{$mock_id}/{$id}/"); $handle->setName($name); $handle->setFullName($name); } } } diff --git a/src/applications/pholio/phid/PholioPHIDTypeMock.php b/src/applications/pholio/phid/PholioPHIDTypeMock.php index ced9f5e9cf..abfd36d2db 100644 --- a/src/applications/pholio/phid/PholioPHIDTypeMock.php +++ b/src/applications/pholio/phid/PholioPHIDTypeMock.php @@ -1,76 +1,73 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $mock = $objects[$phid]; $id = $mock->getID(); $name = $mock->getName(); $handle->setURI("/M{$id}"); $handle->setName("M{$id}"); $handle->setFullName("M{$id}: {$name}"); } } public function canLoadNamedObject($name) { return preg_match('/^M\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new PholioMockQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/pholio/query/PholioImageQuery.php b/src/applications/pholio/query/PholioImageQuery.php index 71bdd3ab0e..4ade82e975 100644 --- a/src/applications/pholio/query/PholioImageQuery.php +++ b/src/applications/pholio/query/PholioImageQuery.php @@ -1,164 +1,168 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withMockIDs(array $mock_ids) { $this->mockIDs = $mock_ids; return $this; } public function withObsolete($obsolete) { $this->obsolete = $obsolete; return $this; } public function needInlineComments($need_inline_comments) { $this->needInlineComments = $need_inline_comments; return $this; } public function setMockCache($mock_cache) { $this->mockCache = $mock_cache; return $this; } public function getMockCache() { return $this->mockCache; } protected function loadPage() { $table = new PholioImage(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $images = $table->loadAllFromArray($data); return $images; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->mockIDs) { $where[] = qsprintf( $conn_r, 'mockID IN (%Ld)', $this->mockIDs); } if ($this->obsolete !== null) { $where[] = qsprintf( $conn_r, 'isObsolete = %d', $this->obsolete); } return $this->formatWhereClause($where); } protected function willFilterPage(array $images) { assert_instances_of($images, 'PholioImage'); if ($this->getMockCache()) { $mocks = $this->getMockCache(); } else { $mock_ids = mpull($images, 'getMockID'); // DO NOT set needImages to true; recursion results! $mocks = id(new PholioMockQuery()) ->setViewer($this->getViewer()) ->withIDs($mock_ids) ->execute(); $mocks = mpull($mocks, null, 'getID'); } foreach ($images as $index => $image) { $mock = idx($mocks, $image->getMockID()); if ($mock) { $image->attachMock($mock); } else { // mock is missing or we can't see it unset($images[$index]); } } return $images; } protected function didFilterPage(array $images) { assert_instances_of($images, 'PholioImage'); $file_phids = mpull($images, 'getFilePHID'); $all_files = id(new PhabricatorFileQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($file_phids) ->execute(); $all_files = mpull($all_files, null, 'getPHID'); if ($this->needInlineComments) { $all_inline_comments = id(new PholioTransactionComment()) ->loadAllWhere('imageid IN (%Ld)', mpull($images, 'getID')); $all_inline_comments = mgroup($all_inline_comments, 'getImageID'); } foreach ($images as $image) { $file = idx($all_files, $image->getFilePHID()); if (!$file) { $file = PhabricatorFile::loadBuiltin($this->getViewer(), 'missing.png'); } $image->attachFile($file); if ($this->needInlineComments) { $inlines = idx($all_inline_comments, $image->getID(), array()); $image->attachInlineComments($inlines); } } return $images; } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPholio'; + } + } diff --git a/src/applications/pholio/query/PholioMockQuery.php b/src/applications/pholio/query/PholioMockQuery.php index f08c9e4633..3b9a40fa9a 100644 --- a/src/applications/pholio/query/PholioMockQuery.php +++ b/src/applications/pholio/query/PholioMockQuery.php @@ -1,164 +1,168 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $author_phids) { $this->authorPHIDs = $author_phids; return $this; } public function needCoverFiles($need_cover_files) { $this->needCoverFiles = $need_cover_files; return $this; } public function needImages($need_images) { $this->needImages = $need_images; return $this; } public function needInlineComments($need_inline_comments) { $this->needInlineComments = $need_inline_comments; return $this; } public function needTokenCounts($need) { $this->needTokenCounts = $need; return $this; } protected function loadPage() { $table = new PholioMock(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $mocks = $table->loadAllFromArray($data); if ($mocks && $this->needImages) { $this->loadImages($mocks); } if ($mocks && $this->needCoverFiles) { $this->loadCoverFiles($mocks); } if ($mocks && $this->needTokenCounts) { $this->loadTokenCounts($mocks); } return $mocks; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'authorPHID in (%Ls)', $this->authorPHIDs); } return $this->formatWhereClause($where); } private function loadImages(array $mocks) { assert_instances_of($mocks, 'PholioMock'); $mock_map = mpull($mocks, null, 'getID'); $all_images = id(new PholioImageQuery()) ->setViewer($this->getViewer()) ->setMockCache($mock_map) ->withMockIDs(array_keys($mock_map)) ->needInlineComments($this->needInlineComments) ->execute(); $image_groups = mgroup($all_images, 'getMockID'); foreach ($mocks as $mock) { $mock_images = idx($image_groups, $mock->getID(), array()); $mock->attachAllImages($mock_images); $active_images = mfilter($mock_images, 'getIsObsolete', true); $mock->attachImages(msort($active_images, 'getSequence')); } } private function loadCoverFiles(array $mocks) { assert_instances_of($mocks, 'PholioMock'); $cover_file_phids = mpull($mocks, 'getCoverPHID'); $cover_files = id(new PhabricatorFileQuery()) ->setViewer($this->getViewer()) ->withPHIDs($cover_file_phids) ->execute(); $cover_files = mpull($cover_files, null, 'getPHID'); foreach ($mocks as $mock) { $file = idx($cover_files, $mock->getCoverPHID()); if (!$file) { $file = PhabricatorFile::loadBuiltin($this->getViewer(), 'missing.png'); } $mock->attachCoverFile($file); } } private function loadTokenCounts(array $mocks) { assert_instances_of($mocks, 'PholioMock'); $phids = mpull($mocks, 'getPHID'); $counts = id(new PhabricatorTokenCountQuery()) ->withObjectPHIDs($phids) ->execute(); foreach ($mocks as $mock) { $mock->attachTokenCount(idx($counts, $mock->getPHID(), 0)); } } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPholio'; + } + } diff --git a/src/applications/phortune/query/PhortuneAccountQuery.php b/src/applications/phortune/query/PhortuneAccountQuery.php index ca48f78767..6feb7aace3 100644 --- a/src/applications/phortune/query/PhortuneAccountQuery.php +++ b/src/applications/phortune/query/PhortuneAccountQuery.php @@ -1,98 +1,103 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withMemberPHIDs(array $phids) { $this->memberPHIDs = $phids; return $this; } protected function loadPage() { $table = new PhortuneAccount(); $conn = $table->establishConnection('r'); $rows = queryfx_all( $conn, 'SELECT a.* FROM %T a %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinClause($conn), $this->buildWhereClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); return $table->loadAllFromArray($rows); } protected function willFilterPage(array $accounts) { $query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($accounts, 'getPHID')) ->withEdgeTypes(array(PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER)); $query->execute(); foreach ($accounts as $account) { $member_phids = $query->getDestinationPHIDs(array($account->getPHID())); $account->attachMemberPHIDs($member_phids); } return $accounts; } private function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); $where[] = $this->buildPagingClause($conn); if ($this->ids) { $where[] = qsprintf( $conn, 'a.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn, 'a.phid IN (%Ls)', $this->phids); } if ($this->memberPHIDs) { $where[] = qsprintf( $conn, 'm.dst IN (%Ls)', $this->memberPHIDs); } return $this->formatWhereClause($where); } private function buildJoinClause(AphrontDatabaseConnection $conn) { $joins = array(); if ($this->memberPHIDs) { $joins[] = qsprintf( $conn, 'LEFT JOIN %T m ON a.phid = m.src AND m.type = %d', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER); } return implode(' ', $joins); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPhortune'; + } + } diff --git a/src/applications/phortune/query/PhortunePaymentMethodQuery.php b/src/applications/phortune/query/PhortunePaymentMethodQuery.php index 552845bfaa..7623af9e1a 100644 --- a/src/applications/phortune/query/PhortunePaymentMethodQuery.php +++ b/src/applications/phortune/query/PhortunePaymentMethodQuery.php @@ -1,113 +1,118 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAccountPHIDs(array $phids) { $this->accountPHIDs = $phids; return $this; } public function withStatus($status) { $this->status = $status; return $this; } protected function loadPage() { $table = new PhortunePaymentMethod(); $conn = $table->establishConnection('r'); $rows = queryfx_all( $conn, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); return $table->loadAllFromArray($rows); } protected function willFilterPage(array $methods) { $accounts = id(new PhortuneAccountQuery()) ->setViewer($this->getViewer()) ->withPHIDs(mpull($methods, 'getAccountPHID')) ->execute(); $accounts = mpull($accounts, null, 'getPHID'); foreach ($methods as $key => $method) { $account = idx($accounts, $method->getAccountPHID()); if (!$account) { unset($methods[$key]); continue; } $method->attachAccount($account); } return $methods; } private function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } if ($this->accountPHIDs) { $where[] = qsprintf( $conn, 'accountPHID IN (%Ls)', $this->accountPHIDs); } switch ($this->status) { case self::STATUS_ANY; break; case self::STATUS_OPEN: $where[] = qsprintf( $conn, 'status in (%Ls)', array( PhortunePaymentMethod::STATUS_ACTIVE, PhortunePaymentMethod::STATUS_FAILED, )); break; default: throw new Exception("Unknown status '{$this->status}'!"); } $where[] = $this->buildPagingClause($conn); return $this->formatWhereClause($where); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPhortune'; + } + } diff --git a/src/applications/phortune/query/PhortuneProductQuery.php b/src/applications/phortune/query/PhortuneProductQuery.php index e87518c019..28c17ce2cf 100644 --- a/src/applications/phortune/query/PhortuneProductQuery.php +++ b/src/applications/phortune/query/PhortuneProductQuery.php @@ -1,56 +1,60 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } protected function loadPage() { $table = new PhortuneProduct(); $conn = $table->establishConnection('r'); $rows = queryfx_all( $conn, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); return $table->loadAllFromArray($rows); } private function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } $where[] = $this->buildPagingClause($conn); return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPhortune'; + } + } diff --git a/src/applications/phrequent/query/PhrequentUserTimeQuery.php b/src/applications/phrequent/query/PhrequentUserTimeQuery.php index dedcce86d0..190be7652b 100644 --- a/src/applications/phrequent/query/PhrequentUserTimeQuery.php +++ b/src/applications/phrequent/query/PhrequentUserTimeQuery.php @@ -1,305 +1,310 @@ userPHIDs = $user_phids; return $this; } public function withObjectPHIDs($object_phids) { $this->objectPHIDs = $object_phids; return $this; } public function withEnded($ended) { $this->ended = $ended; return $this; } public function setOrder($order) { $this->order = $order; return $this; } public function needPreemptingEvents($need_events) { $this->needPreemptingEvents = $need_events; return $this; } private function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->userPHIDs) { $where[] = qsprintf( $conn, 'userPHID IN (%Ls)', $this->userPHIDs); } if ($this->objectPHIDs) { $where[] = qsprintf( $conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } switch ($this->ended) { case self::ENDED_ALL: break; case self::ENDED_YES: $where[] = qsprintf( $conn, 'dateEnded IS NOT NULL'); break; case self::ENDED_NO: $where[] = qsprintf( $conn, 'dateEnded IS NULL'); break; default: throw new Exception("Unknown ended '{$this->ended}'!"); } $where[] = $this->buildPagingClause($conn); return $this->formatWhereClause($where); } protected function getPagingColumn() { switch ($this->order) { case self::ORDER_ID_ASC: case self::ORDER_ID_DESC: return 'id'; case self::ORDER_STARTED_ASC: case self::ORDER_STARTED_DESC: return 'dateStarted'; case self::ORDER_ENDED_ASC: case self::ORDER_ENDED_DESC: return 'dateEnded'; case self::ORDER_DURATION_ASC: case self::ORDER_DURATION_DESC: return 'COALESCE(dateEnded, UNIX_TIMESTAMP()) - dateStarted'; default: throw new Exception("Unknown order '{$this->order}'!"); } } protected function getPagingValue($result) { switch ($this->order) { case self::ORDER_ID_ASC: case self::ORDER_ID_DESC: return $result->getID(); case self::ORDER_STARTED_ASC: case self::ORDER_STARTED_DESC: return $result->getDateStarted(); case self::ORDER_ENDED_ASC: case self::ORDER_ENDED_DESC: return $result->getDateEnded(); case self::ORDER_DURATION_ASC: case self::ORDER_DURATION_DESC: return ($result->getDateEnded() || time()) - $result->getDateStarted(); default: throw new Exception("Unknown order '{$this->order}'!"); } } protected function getReversePaging() { switch ($this->order) { case self::ORDER_ID_ASC: case self::ORDER_STARTED_ASC: case self::ORDER_ENDED_ASC: case self::ORDER_DURATION_ASC: return true; case self::ORDER_ID_DESC: case self::ORDER_STARTED_DESC: case self::ORDER_ENDED_DESC: case self::ORDER_DURATION_DESC: return false; default: throw new Exception("Unknown order '{$this->order}'!"); } } protected function loadPage() { $usertime = new PhrequentUserTime(); $conn = $usertime->establishConnection('r'); $data = queryfx_all( $conn, 'SELECT usertime.* FROM %T usertime %Q %Q %Q', $usertime->getTableName(), $this->buildWhereClause($conn), $this->buildOrderClause($conn), $this->buildLimitClause($conn)); return $usertime->loadAllFromArray($data); } protected function didFilterPage(array $page) { if ($this->needPreemptingEvents) { $usertime = new PhrequentUserTime(); $conn_r = $usertime->establishConnection('r'); $preempt = array(); foreach ($page as $event) { $preempt[] = qsprintf( $conn_r, '(userPHID = %s AND (dateStarted BETWEEN %d AND %d) AND (dateEnded IS NULL OR dateEnded > %d))', $event->getUserPHID(), $event->getDateStarted(), nonempty($event->getDateEnded(), PhabricatorTime::getNow()), $event->getDateStarted()); } $preempting_events = queryfx_all( $conn_r, 'SELECT * FROM %T WHERE %Q ORDER BY dateStarted ASC, id ASC', $usertime->getTableName(), implode(' OR ', $preempt)); $preempting_events = $usertime->loadAllFromArray($preempting_events); $preempting_events = mgroup($preempting_events, 'getUserPHID'); foreach ($page as $event) { $e_start = $event->getDateStarted(); $e_end = $event->getDateEnded(); $select = array(); $user_events = idx($preempting_events, $event->getUserPHID(), array()); foreach ($user_events as $u_event) { if ($u_event->getID() == $event->getID()) { // Don't allow an event to preempt itself. continue; } $u_start = $u_event->getDateStarted(); $u_end = $u_event->getDateEnded(); if (($u_start >= $e_start) && ($u_end <= $e_end) && ($u_end === null || $u_end > $e_start)) { $select[] = $u_event; } } $event->attachPreemptingEvents($select); } } return $page; } /* -( Helper Functions ) --------------------------------------------------- */ public static function getEndedSearchOptions() { return array( self::ENDED_ALL => pht('All'), self::ENDED_NO => pht('No'), self::ENDED_YES => pht('Yes')); } public static function getOrderSearchOptions() { return array( self::ORDER_STARTED_ASC => pht('by furthest start date'), self::ORDER_STARTED_DESC => pht('by nearest start date'), self::ORDER_ENDED_ASC => pht('by furthest end date'), self::ORDER_ENDED_DESC => pht('by nearest end date'), self::ORDER_DURATION_ASC => pht('by smallest duration'), self::ORDER_DURATION_DESC => pht('by largest duration')); } public static function getUserTotalObjectsTracked( PhabricatorUser $user) { $usertime_dao = new PhrequentUserTime(); $conn = $usertime_dao->establishConnection('r'); $count = queryfx_one( $conn, 'SELECT COUNT(usertime.id) N FROM %T usertime '. 'WHERE usertime.userPHID = %s '. 'AND usertime.dateEnded IS NULL', $usertime_dao->getTableName(), $user->getPHID()); return $count['N']; } public static function isUserTrackingObject( PhabricatorUser $user, $phid) { $usertime_dao = new PhrequentUserTime(); $conn = $usertime_dao->establishConnection('r'); $count = queryfx_one( $conn, 'SELECT COUNT(usertime.id) N FROM %T usertime '. 'WHERE usertime.userPHID = %s '. 'AND usertime.objectPHID = %s '. 'AND usertime.dateEnded IS NULL', $usertime_dao->getTableName(), $user->getPHID(), $phid); return $count['N'] > 0; } public static function getUserTimeSpentOnObject( PhabricatorUser $user, $phid) { $usertime_dao = new PhrequentUserTime(); $conn = $usertime_dao->establishConnection('r'); // First calculate all the time spent where the // usertime blocks have ended. $sum_ended = queryfx_one( $conn, 'SELECT SUM(usertime.dateEnded - usertime.dateStarted) N '. 'FROM %T usertime '. 'WHERE usertime.userPHID = %s '. 'AND usertime.objectPHID = %s '. 'AND usertime.dateEnded IS NOT NULL', $usertime_dao->getTableName(), $user->getPHID(), $phid); // Now calculate the time spent where the usertime // blocks have not yet ended. $sum_not_ended = queryfx_one( $conn, 'SELECT SUM(UNIX_TIMESTAMP() - usertime.dateStarted) N '. 'FROM %T usertime '. 'WHERE usertime.userPHID = %s '. 'AND usertime.objectPHID = %s '. 'AND usertime.dateEnded IS NULL', $usertime_dao->getTableName(), $user->getPHID(), $phid); return $sum_ended['N'] + $sum_not_ended['N']; } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPhrequent'; + } + } diff --git a/src/applications/phriction/phid/PhrictionPHIDTypeDocument.php b/src/applications/phriction/phid/PhrictionPHIDTypeDocument.php index 554457bba0..e0ebcd92a2 100644 --- a/src/applications/phriction/phid/PhrictionPHIDTypeDocument.php +++ b/src/applications/phriction/phid/PhrictionPHIDTypeDocument.php @@ -1,56 +1,53 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $document = $objects[$phid]; $content = $document->getContent(); $title = $content->getTitle(); $slug = $document->getSlug(); $status = $document->getStatus(); $handle->setName($title); $handle->setURI(PhrictionDocument::getSlugURI($slug)); if ($status != PhrictionDocumentStatus::STATUS_EXISTS) { $handle->setStatus(PhabricatorObjectHandleStatus::STATUS_CLOSED); } } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php index a45d701ed5..5f2b3b1b6f 100644 --- a/src/applications/phriction/query/PhrictionDocumentQuery.php +++ b/src/applications/phriction/query/PhrictionDocumentQuery.php @@ -1,188 +1,193 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withSlugs(array $slugs) { $this->slugs = $slugs; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function setOrder($order) { $this->order = $order; return $this; } protected function loadPage() { $document = new PhrictionDocument(); $conn_r = $document->establishConnection('r'); $rows = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $document->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $document->loadAllFromArray($rows); } protected function willFilterPage(array $documents) { $contents = id(new PhrictionContent())->loadAllWhere( 'id IN (%Ld)', mpull($documents, 'getContentID')); foreach ($documents as $key => $document) { $content_id = $document->getContentID(); if (empty($contents[$content_id])) { unset($documents[$key]); continue; } $document->attachContent($contents[$content_id]); } foreach ($documents as $document) { $document->attachProject(null); } $project_slugs = array(); foreach ($documents as $key => $document) { $slug = $document->getSlug(); if (!PhrictionDocument::isProjectSlug($slug)) { continue; } $project_slugs[$key] = PhrictionDocument::getProjectSlugIdentifier($slug); } if ($project_slugs) { $projects = id(new PhabricatorProjectQuery()) ->setViewer($this->getViewer()) ->withPhrictionSlugs($project_slugs) ->execute(); $projects = mpull($projects, null, 'getPhrictionSlug'); foreach ($documents as $key => $document) { $slug = idx($project_slugs, $key); if ($slug) { $project = idx($projects, $slug); if (!$project) { unset($documents[$key]); continue; } $document->attachProject($project); } } } return $documents; } protected function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } if ($this->slugs) { $where[] = qsprintf( $conn, 'slug IN (%Ls)', $this->slugs); } switch ($this->status) { case self::STATUS_OPEN: $where[] = qsprintf( $conn, 'status NOT IN (%Ld)', array( PhrictionDocumentStatus::STATUS_DELETED, PhrictionDocumentStatus::STATUS_MOVED, PhrictionDocumentStatus::STATUS_STUB, )); break; case self::STATUS_NONSTUB: $where[] = qsprintf( $conn, 'status NOT IN (%Ld)', array( PhrictionDocumentStatus::STATUS_MOVED, PhrictionDocumentStatus::STATUS_STUB, )); break; case self::STATUS_ANY: break; default: throw new Exception("Unknown status '{$this->status}'!"); } $where[] = $this->buildPagingClause($conn); return $this->formatWhereClause($where); } protected function getPagingColumn() { switch ($this->order) { case self::ORDER_CREATED: return 'id'; case self::ORDER_UPDATED: return 'contentID'; default: throw new Exception("Unknown order '{$this->order}'!"); } } protected function getPagingValue($result) { switch ($this->order) { case self::ORDER_CREATED: return $result->getID(); case self::ORDER_UPDATED: return $result->getContentID(); default: throw new Exception("Unknown order '{$this->order}'!"); } } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPhriction'; + } + } diff --git a/src/applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php b/src/applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php index 9ff1e98ec6..dc6fc09f35 100644 --- a/src/applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php +++ b/src/applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php @@ -1,36 +1,40 @@ results = $results; return $this; } protected function willExecute() { $this->offset = 0; } protected function loadPage() { if ($this->getRawResultLimit()) { return array_slice( $this->results, $this->offset, $this->getRawResultLimit()); } else { return array_slice($this->results, $this->offset); } } public function nextPage(array $page) { $this->offset += count($page); } + public function getQueryApplicationClass() { + return null; + } + } diff --git a/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php b/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php index b8b0236b52..6dcbc6a78d 100644 --- a/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php +++ b/src/applications/policy/__tests__/PhabricatorPolicyTestCase.php @@ -1,292 +1,308 @@ overrideEnvConfig('policy.allow-public', true); $this->expectVisibility( $this->buildObject(PhabricatorPolicies::POLICY_PUBLIC), array( 'public' => true, 'user' => true, 'admin' => true, ), 'Public Policy (Enabled in Config)'); } /** * Verify that POLICY_PUBLIC is interpreted as POLICY_USER when public * policies are disallowed. */ public function testPublicPolicyDisabled() { $env = PhabricatorEnv::beginScopedEnv(); $env->overrideEnvConfig('policy.allow-public', false); $this->expectVisibility( $this->buildObject(PhabricatorPolicies::POLICY_PUBLIC), array( 'public' => false, 'user' => true, 'admin' => true, ), 'Public Policy (Disabled in Config)'); } /** * Verify that any logged-in user can view an object with POLICY_USER, but * logged-out users can not. */ public function testUsersPolicy() { $this->expectVisibility( $this->buildObject(PhabricatorPolicies::POLICY_USER), array( 'public' => false, 'user' => true, 'admin' => true, ), 'User Policy'); } /** * Verify that only administrators can view an object with POLICY_ADMIN. */ public function testAdminPolicy() { $this->expectVisibility( $this->buildObject(PhabricatorPolicies::POLICY_ADMIN), array( 'public' => false, 'user' => false, 'admin' => true, ), 'Admin Policy'); } /** * Verify that no one can view an object with POLICY_NOONE. */ public function testNoOnePolicy() { $this->expectVisibility( $this->buildObject(PhabricatorPolicies::POLICY_NOONE), array( 'public' => false, 'user' => false, 'admin' => false, ), 'No One Policy'); } /** * Test offset-based filtering. */ public function testOffsets() { $results = array( $this->buildObject(PhabricatorPolicies::POLICY_NOONE), $this->buildObject(PhabricatorPolicies::POLICY_NOONE), $this->buildObject(PhabricatorPolicies::POLICY_NOONE), $this->buildObject(PhabricatorPolicies::POLICY_USER), $this->buildObject(PhabricatorPolicies::POLICY_USER), $this->buildObject(PhabricatorPolicies::POLICY_USER), ); $query = new PhabricatorPolicyAwareTestQuery(); $query->setResults($results); $query->setViewer($this->buildUser('user')); $this->assertEqual( 3, count($query->setLimit(3)->setOffset(0)->execute()), 'Invisible objects are ignored.'); $this->assertEqual( 0, count($query->setLimit(3)->setOffset(3)->execute()), 'Offset pages through visible objects only.'); $this->assertEqual( 2, count($query->setLimit(3)->setOffset(1)->execute()), 'Offsets work correctly.'); $this->assertEqual( 2, count($query->setLimit(0)->setOffset(1)->execute()), 'Offset with no limit works.'); } /** * Test limits. */ public function testLimits() { $results = array( $this->buildObject(PhabricatorPolicies::POLICY_USER), $this->buildObject(PhabricatorPolicies::POLICY_USER), $this->buildObject(PhabricatorPolicies::POLICY_USER), $this->buildObject(PhabricatorPolicies::POLICY_USER), $this->buildObject(PhabricatorPolicies::POLICY_USER), $this->buildObject(PhabricatorPolicies::POLICY_USER), ); $query = new PhabricatorPolicyAwareTestQuery(); $query->setResults($results); $query->setViewer($this->buildUser('user')); $this->assertEqual( 3, count($query->setLimit(3)->setOffset(0)->execute()), 'Limits work.'); $this->assertEqual( 2, count($query->setLimit(3)->setOffset(4)->execute()), 'Limit + offset work.'); } /** * Test that omnipotent users bypass policies. */ public function testOmnipotence() { $results = array( $this->buildObject(PhabricatorPolicies::POLICY_NOONE), ); $query = new PhabricatorPolicyAwareTestQuery(); $query->setResults($results); $query->setViewer(PhabricatorUser::getOmnipotentUser()); $this->assertEqual( 1, count($query->execute())); } /** * Test that invalid policies reject viewers of all types. */ public function testRejectInvalidPolicy() { $invalid_policy = "the duck goes quack"; $object = $this->buildObject($invalid_policy); $this->expectVisibility( $object = $this->buildObject($invalid_policy), array( 'public' => false, 'user' => false, 'admin' => false, ), 'Invalid Policy'); } /** * An omnipotent user should be able to see even objects with invalid * policies. */ public function testInvalidPolicyVisibleByOmnipotentUser() { $invalid_policy = "the cow goes moo"; $object = $this->buildObject($invalid_policy); $results = array( $object, ); $query = new PhabricatorPolicyAwareTestQuery(); $query->setResults($results); $query->setViewer(PhabricatorUser::getOmnipotentUser()); $this->assertEqual( 1, count($query->execute())); } + public function testAllQueriesBelongToActualApplications() { + $queries = id(new PhutilSymbolLoader()) + ->setAncestorClass('PhabricatorPolicyAwareQuery') + ->loadObjects(); + + foreach ($queries as $qclass => $query) { + $class = $query->getQueryApplicationClass(); + if (!$class) { + continue; + } + $this->assertEqual( + true, + class_exists($class), + "Application class '{$class}' for query '{$qclass}'"); + } + } /** * Test an object for visibility across multiple user specifications. */ private function expectVisibility( PhabricatorPolicyTestObject $object, array $map, $description) { foreach ($map as $spec => $expect) { $viewer = $this->buildUser($spec); $query = new PhabricatorPolicyAwareTestQuery(); $query->setResults(array($object)); $query->setViewer($viewer); $caught = null; try { $result = $query->executeOne(); } catch (PhabricatorPolicyException $ex) { $caught = $ex; } if ($expect) { $this->assertEqual( $object, $result, "{$description} with user {$spec} should succeed."); } else { $this->assertEqual( true, $caught instanceof PhabricatorPolicyException, "{$description} with user {$spec} should fail."); } } } /** * Build a test object to spec. */ private function buildObject($policy) { $object = new PhabricatorPolicyTestObject(); $object->setCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, )); $object->setPolicies( array( PhabricatorPolicyCapability::CAN_VIEW => $policy, )); return $object; } /** * Build a test user to spec. */ private function buildUser($spec) { $user = new PhabricatorUser(); switch ($spec) { case 'public': break; case 'user': $user->setPHID(1); break; case 'admin': $user->setPHID(1); $user->setIsAdmin(true); break; default: throw new Exception("Unknown user spec '{$spec}'."); } return $user; } } diff --git a/src/applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php b/src/applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php index 27f693d8a8..8941de2e5b 100644 --- a/src/applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php +++ b/src/applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php @@ -1,48 +1,45 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $policy = $objects[$phid]; $handle->setName($policy->getName()); $handle->setURI($policy->getHref()); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/policy/query/PhabricatorPolicyQuery.php b/src/applications/policy/query/PhabricatorPolicyQuery.php index 9a078abf3e..246741786d 100644 --- a/src/applications/policy/query/PhabricatorPolicyQuery.php +++ b/src/applications/policy/query/PhabricatorPolicyQuery.php @@ -1,227 +1,232 @@ object = $object; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public static function loadPolicies( PhabricatorUser $viewer, PhabricatorPolicyInterface $object) { $results = array(); $map = array(); foreach ($object->getCapabilities() as $capability) { $map[$capability] = $object->getPolicy($capability); } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->withPHIDs($map) ->execute(); foreach ($map as $capability => $phid) { $results[$capability] = $policies[$phid]; } return $results; } public static function renderPolicyDescriptions( PhabricatorUser $viewer, PhabricatorPolicyInterface $object, $icon = false) { $policies = self::loadPolicies($viewer, $object); foreach ($policies as $capability => $policy) { $policies[$capability] = $policy->renderDescription($icon); } return $policies; } public function loadPage() { if ($this->object && $this->phids) { throw new Exception( "You can not issue a policy query with both setObject() and ". "setPHIDs()."); } else if ($this->object) { $phids = $this->loadObjectPolicyPHIDs(); } else { $phids = $this->phids; } $phids = array_fuse($phids); $results = array(); // First, load global policies. foreach ($this->getGlobalPolicies() as $phid => $policy) { if (isset($phids[$phid])) { $results[$phid] = $policy; unset($phids[$phid]); } } // If we still need policies, we're going to have to fetch data. Bucket // the remaining policies into rule-based policies and handle-based // policies. if ($phids) { $rule_policies = array(); $handle_policies = array(); foreach ($phids as $phid) { $phid_type = phid_get_type($phid); if ($phid_type == PhabricatorPolicyPHIDTypePolicy::TYPECONST) { $rule_policies[$phid] = $phid; } else { $handle_policies[$phid] = $phid; } } if ($handle_policies) { $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->getViewer()) ->withPHIDs($handle_policies) ->execute(); foreach ($handle_policies as $phid) { $results[$phid] = PhabricatorPolicy::newFromPolicyAndHandle( $phid, $handles[$phid]); } } if ($rule_policies) { $rules = id(new PhabricatorPolicy())->loadAllWhere( 'phid IN (%Ls)', $rule_policies); $results += mpull($rules, null, 'getPHID'); } } $results = msort($results, 'getSortKey'); return $results; } public static function isGlobalPolicy($policy) { $globalPolicies = self::getGlobalPolicies(); if (isset($globalPolicies[$policy])) { return true; } return false; } public static function getGlobalPolicy($policy) { if (!self::isGlobalPolicy($policy)) { throw new Exception("Policy '{$policy}' is not a global policy!"); } return idx(self::getGlobalPolicies(), $policy); } private static function getGlobalPolicies() { static $constants = array( PhabricatorPolicies::POLICY_PUBLIC, PhabricatorPolicies::POLICY_USER, PhabricatorPolicies::POLICY_ADMIN, PhabricatorPolicies::POLICY_NOONE, ); $results = array(); foreach ($constants as $constant) { $results[$constant] = id(new PhabricatorPolicy()) ->setType(PhabricatorPolicyType::TYPE_GLOBAL) ->setPHID($constant) ->setName(self::getGlobalPolicyName($constant)) ->setShortName(self::getGlobalPolicyShortName($constant)) ->makeEphemeral(); } return $results; } private static function getGlobalPolicyName($policy) { switch ($policy) { case PhabricatorPolicies::POLICY_PUBLIC: return pht('Public (No Login Required)'); case PhabricatorPolicies::POLICY_USER: return pht('All Users'); case PhabricatorPolicies::POLICY_ADMIN: return pht('Administrators'); case PhabricatorPolicies::POLICY_NOONE: return pht('No One'); default: return pht('Unknown Policy'); } } private static function getGlobalPolicyShortName($policy) { switch ($policy) { case PhabricatorPolicies::POLICY_PUBLIC: return pht('Public'); default: return null; } } private function loadObjectPolicyPHIDs() { $phids = array(); $viewer = $this->getViewer(); if ($viewer->getPHID()) { $projects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withMemberPHIDs(array($viewer->getPHID())) ->execute(); foreach ($projects as $project) { $phids[] = $project->getPHID(); } } $capabilities = $this->object->getCapabilities(); foreach ($capabilities as $capability) { $policy = $this->object->getPolicy($capability); if (!$policy) { continue; } $phids[] = $policy; } // If this install doesn't have "Public" enabled, don't include it as an // option unless the object already has a "Public" policy. In this case we // retain the policy but enforce it as though it was "All Users". $show_public = PhabricatorEnv::getEnvConfig('policy.allow-public'); foreach ($this->getGlobalPolicies() as $phid => $policy) { if ($phid == PhabricatorPolicies::POLICY_PUBLIC) { if (!$show_public) { continue; } } $phids[] = $phid; } return $phids; } protected function shouldDisablePolicyFiltering() { // Policy filtering of policies is currently perilous and not required by // the application. return true; } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPolicy'; + } + } diff --git a/src/applications/ponder/phid/PonderPHIDTypeAnswer.php b/src/applications/ponder/phid/PonderPHIDTypeAnswer.php index ebe9c40fb6..be6b4d6779 100644 --- a/src/applications/ponder/phid/PonderPHIDTypeAnswer.php +++ b/src/applications/ponder/phid/PonderPHIDTypeAnswer.php @@ -1,45 +1,42 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $answer = $objects[$phid]; $id = $answer->getID(); $handle->setName("Answer {$id}"); $handle->setURI($answer->getURI()); } } } diff --git a/src/applications/ponder/phid/PonderPHIDTypeQuestion.php b/src/applications/ponder/phid/PonderPHIDTypeQuestion.php index 23f59095a7..2f42e91371 100644 --- a/src/applications/ponder/phid/PonderPHIDTypeQuestion.php +++ b/src/applications/ponder/phid/PonderPHIDTypeQuestion.php @@ -1,75 +1,72 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $question = $objects[$phid]; $id = $question->getID(); $handle->setName("Q{$id}"); $handle->setURI("/Q{$id}"); $handle->setFullName($question->getFullTitle()); } } public function canLoadNamedObject($name) { return preg_match('/^Q\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new PonderQuestionQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/ponder/query/PonderAnswerQuery.php b/src/applications/ponder/query/PonderAnswerQuery.php index 66e532c6b6..df6e679b5a 100644 --- a/src/applications/ponder/query/PonderAnswerQuery.php +++ b/src/applications/ponder/query/PonderAnswerQuery.php @@ -1,126 +1,131 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $phids) { $this->authorPHIDs = $phids; return $this; } public function withQuestionIDs(array $ids) { $this->questionIDs = $ids; return $this; } public function needViewerVotes($need_viewer_votes) { $this->needViewerVotes = $need_viewer_votes; return $this; } private function buildWhereClause($conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'authorPHID IN (%Ls)', $this->authorPHIDs); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } public function loadPage() { $answer = new PonderAnswer(); $conn_r = $answer->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT a.* FROM %T a %Q %Q %Q', $answer->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $answer->loadAllFromArray($data); } public function willFilterPage(array $answers) { $questions = id(new PonderQuestionQuery()) ->setViewer($this->getViewer()) ->withIDs(mpull($answers, 'getQuestionID')) ->execute(); foreach ($answers as $key => $answer) { $question = idx($questions, $answer->getQuestionID()); if (!$question) { unset($answers[$key]); continue; } $answer->attachQuestion($question); } if ($this->needViewerVotes) { $viewer_phid = $this->getViewer()->getPHID(); $etype = PhabricatorEdgeConfig::TYPE_ANSWER_HAS_VOTING_USER; $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($answers, 'getPHID')) ->withDestinationPHIDs(array($viewer_phid)) ->withEdgeTypes(array($etype)) ->needEdgeData(true) ->execute(); foreach ($answers as $answer) { $user_edge = idx( $edges[$answer->getPHID()][$etype], $viewer_phid, array()); $answer->attachUserVote($viewer_phid, idx($user_edge, 'data', 0)); } } return $answers; } protected function getReversePaging() { return true; } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPonder'; + } + } diff --git a/src/applications/ponder/query/PonderQuestionQuery.php b/src/applications/ponder/query/PonderQuestionQuery.php index e297fe06bb..eeec6b8071 100644 --- a/src/applications/ponder/query/PonderQuestionQuery.php +++ b/src/applications/ponder/query/PonderQuestionQuery.php @@ -1,197 +1,202 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs(array $phids) { $this->authorPHIDs = $phids; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withAnswererPHIDs(array $phids) { $this->answererPHIDs = $phids; return $this; } public function needAnswers($need_answers) { $this->needAnswers = $need_answers; return $this; } public function needViewerVotes($need_viewer_votes) { $this->needViewerVotes = $need_viewer_votes; return $this; } public function setOrder($order) { $this->order = $order; return $this; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'q.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'q.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'q.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->status) { switch ($this->status) { case self::STATUS_ANY: break; case self::STATUS_OPEN: $where[] = qsprintf( $conn_r, 'q.status = %d', PonderQuestionStatus::STATUS_OPEN); break; case self::STATUS_CLOSED: $where[] = qsprintf( $conn_r, 'q.status = %d', PonderQuestionStatus::STATUS_CLOSED); break; default: throw new Exception("Unknown status query '{$this->status}'!"); } } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function buildOrderByClause(AphrontDatabaseConnection $conn_r) { switch ($this->order) { case self::ORDER_HOTTEST: return qsprintf($conn_r, 'ORDER BY q.heat DESC, q.id DESC'); case self::ORDER_CREATED: return qsprintf($conn_r, 'ORDER BY q.id DESC'); default: throw new Exception("Unknown order '{$this->order}'!"); } } protected function loadPage() { $question = new PonderQuestion(); $conn_r = $question->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT q.* FROM %T q %Q %Q %Q %Q', $question->getTableName(), $this->buildJoinsClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderByClause($conn_r), $this->buildLimitClause($conn_r)); return $question->loadAllFromArray($data); } public function willFilterPage(array $questions) { if ($this->needAnswers) { $aquery = id(new PonderAnswerQuery()) ->setViewer($this->getViewer()) ->withQuestionIDs(mpull($questions, 'getID')); if ($this->needViewerVotes) { $aquery->needViewerVotes($this->needViewerVotes); } $answers = $aquery->execute(); $answers = mgroup($answers, 'getQuestionID'); foreach ($questions as $question) { $question_answers = idx($answers, $question->getID(), array()); $question->attachAnswers(mpull($question_answers, null, 'getPHID')); } } if ($this->needViewerVotes) { $viewer_phid = $this->getViewer()->getPHID(); $etype = PhabricatorEdgeConfig::TYPE_QUESTION_HAS_VOTING_USER; $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($questions, 'getPHID')) ->withDestinationPHIDs(array($viewer_phid)) ->withEdgeTypes(array($etype)) ->needEdgeData(true) ->execute(); foreach ($questions as $question) { $user_edge = idx( $edges[$question->getPHID()][$etype], $viewer_phid, array()); $question->attachUserVote($viewer_phid, idx($user_edge, 'data', 0)); } } return $questions; } private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { $joins = array(); if ($this->answererPHIDs) { $answer_table = new PonderAnswer(); $joins[] = qsprintf( $conn_r, 'JOIN %T a ON a.questionID = q.id AND a.authorPHID IN (%Ls)', $answer_table->getTableName(), $this->answererPHIDs); } return implode(' ', $joins); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationPonder'; + } + } diff --git a/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php b/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php index 5b7bbbe936..97a81b1d0c 100644 --- a/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php +++ b/src/applications/project/phid/PhabricatorProjectPHIDTypeProject.php @@ -1,52 +1,49 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $project = $objects[$phid]; $name = $project->getName(); $id = $project->getID(); $handle->setName($name); $handle->setObjectName('#'.rtrim($project->getPhrictionSlug(), '/')); $handle->setURI("/project/view/{$id}/"); } } public function canLoadNamedObject($name) { // TODO: We should be able to load named projects by hashtag, e.g. "#yolo". return false; } } diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 70bfa9d573..00025668c0 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -1,263 +1,268 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withMemberPHIDs(array $member_phids) { $this->memberPHIDs = $member_phids; return $this; } public function withPhrictionSlugs(array $slugs) { $this->slugs = $slugs; return $this; } public function needMembers($need_members) { $this->needMembers = $need_members; return $this; } public function needProfiles($need_profiles) { $this->needProfiles = $need_profiles; return $this; } protected function getPagingColumn() { return 'name'; } protected function getPagingValue($result) { return $result->getName(); } protected function getReversePaging() { return true; } protected function loadPage() { $table = new PhabricatorProject(); $conn_r = $table->establishConnection('r'); // NOTE: Because visibility checks for projects depend on whether or not // the user is a project member, we always load their membership. If we're // loading all members anyway we can piggyback on that; otherwise we // do an explicit join. $select_clause = ''; if (!$this->needMembers) { $select_clause = ', vm.dst viewerIsMember'; } $data = queryfx_all( $conn_r, 'SELECT p.* %Q FROM %T p %Q %Q %Q %Q %Q', $select_clause, $table->getTableName(), $this->buildJoinClause($conn_r), $this->buildWhereClause($conn_r), $this->buildGroupClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $projects = $table->loadAllFromArray($data); if ($projects) { $viewer_phid = $this->getViewer()->getPHID(); if ($this->needMembers) { $etype = PhabricatorEdgeConfig::TYPE_PROJ_MEMBER; $members = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($projects, 'getPHID')) ->withEdgeTypes(array($etype)) ->execute(); foreach ($projects as $project) { $phid = $project->getPHID(); $project->attachMemberPHIDs(array_keys($members[$phid][$etype])); $project->setIsUserMember( $viewer_phid, isset($members[$phid][$etype][$viewer_phid])); } } else { foreach ($data as $row) { $projects[$row['id']]->setIsUserMember( $viewer_phid, ($row['viewerIsMember'] !== null)); } } } return $projects; } protected function didFilterPage(array $projects) { if ($this->needProfiles) { $profiles = id(new PhabricatorProjectProfile())->loadAllWhere( 'projectPHID IN (%Ls)', mpull($projects, 'getPHID')); $profiles = mpull($profiles, null, 'getProjectPHID'); $default = null; if ($profiles) { $file_phids = mpull($profiles, 'getProfileImagePHID'); $files = id(new PhabricatorFileQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); foreach ($profiles as $profile) { $file = idx($files, $profile->getProfileImagePHID()); if (!$file) { if (!$default) { $default = PhabricatorFile::loadBuiltin( $this->getViewer(), 'project.png'); } $file = $default; } $profile->attachProfileImageFile($file); } } foreach ($projects as $project) { $profile = idx($profiles, $project->getPHID()); if (!$profile) { if (!$default) { $default = PhabricatorFile::loadBuiltin( $this->getViewer(), 'project.png'); } $profile = id(new PhabricatorProjectProfile()) ->setProjectPHID($project->getPHID()) ->attachProfileImageFile($default); } $project->attachProfile($profile); } } return $projects; } private function buildWhereClause($conn_r) { $where = array(); if ($this->status != self::STATUS_ANY) { switch ($this->status) { case self::STATUS_OPEN: case self::STATUS_ACTIVE: $filter = array( PhabricatorProjectStatus::STATUS_ACTIVE, ); break; case self::STATUS_CLOSED: case self::STATUS_ARCHIVED: $filter = array( PhabricatorProjectStatus::STATUS_ARCHIVED, ); break; default: throw new Exception( "Unknown project status '{$this->status}'!"); } $where[] = qsprintf( $conn_r, 'status IN (%Ld)', $filter); } if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->memberPHIDs) { $where[] = qsprintf( $conn_r, 'e.dst IN (%Ls)', $this->memberPHIDs); } if ($this->slugs) { $where[] = qsprintf( $conn_r, 'phrictionSlug IN (%Ls)', $this->slugs); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function buildGroupClause($conn_r) { if ($this->memberPHIDs) { return 'GROUP BY p.id'; } else { return ''; } } private function buildJoinClause($conn_r) { $joins = array(); if (!$this->needMembers) { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T vm ON vm.src = p.phid AND vm.type = %d AND vm.dst = %s', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_PROJ_MEMBER, $this->getViewer()->getPHID()); } if ($this->memberPHIDs) { $joins[] = qsprintf( $conn_r, 'JOIN %T e ON e.src = p.phid AND e.type = %d', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_PROJ_MEMBER); } return implode(' ', $joins); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationProject'; + } + } diff --git a/src/applications/releeph/phid/ReleephPHIDTypeBranch.php b/src/applications/releeph/phid/ReleephPHIDTypeBranch.php index 7106dfc9a1..3511a67c69 100644 --- a/src/applications/releeph/phid/ReleephPHIDTypeBranch.php +++ b/src/applications/releeph/phid/ReleephPHIDTypeBranch.php @@ -1,48 +1,45 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $branch = $objects[$phid]; $handle->setURI($branch->getURI()); $handle->setName($branch->getBasename()); $handle->setFullName($branch->getName()); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/releeph/phid/ReleephPHIDTypeProject.php b/src/applications/releeph/phid/ReleephPHIDTypeProject.php index f127b75849..e15994bf83 100644 --- a/src/applications/releeph/phid/ReleephPHIDTypeProject.php +++ b/src/applications/releeph/phid/ReleephPHIDTypeProject.php @@ -1,47 +1,44 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $project = $objects[$phid]; $handle->setName($project->getName()); $handle->setURI($project->getURI()); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/releeph/phid/ReleephPHIDTypeRequest.php b/src/applications/releeph/phid/ReleephPHIDTypeRequest.php index a3529a612c..506585fce0 100644 --- a/src/applications/releeph/phid/ReleephPHIDTypeRequest.php +++ b/src/applications/releeph/phid/ReleephPHIDTypeRequest.php @@ -1,51 +1,48 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $request = $objects[$phid]; $id = $request->getID(); $title = $request->getSummaryForDisplay(); $handle->setURI("/RQ{$id}"); $handle->setName($title); $handle->setFullName("RQ{$id}: {$title}"); } } public function canLoadNamedObject($name) { return false; } } diff --git a/src/applications/releeph/query/ReleephBranchQuery.php b/src/applications/releeph/query/ReleephBranchQuery.php index c1ab888fea..5a0bf40d06 100644 --- a/src/applications/releeph/query/ReleephBranchQuery.php +++ b/src/applications/releeph/query/ReleephBranchQuery.php @@ -1,132 +1,137 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function needCutPointCommits($need_commits) { $this->needCutPointCommits = $need_commits; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withProjectIDs(array $ids) { $this->projectIDs = $ids; return $this; } public function loadPage() { $table = new ReleephBranch(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $branches) { $project_ids = mpull($branches, 'getReleephProjectID'); $projects = id(new ReleephProjectQuery()) ->withIDs($project_ids) ->setViewer($this->getViewer()) ->execute(); foreach ($branches as $key => $branch) { $project_id = $project_ids[$key]; if (isset($projects[$project_id])) { $branch->attachProject($projects[$project_id]); } else { unset($branches[$key]); } } if ($this->needCutPointCommits) { $commit_phids = mpull($branches, 'getCutPointCommitPHID'); $commits = id(new DiffusionCommitQuery()) ->setViewer($this->getViewer()) ->withPHIDs($commit_phids) ->execute(); $commits = mpull($commits, null, 'getPHID'); foreach ($branches as $branch) { $commit = idx($commits, $branch->getCutPointCommitPHID()); $branch->attachCutPointCommit($commit); } } return $branches; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->projectIDs) { $where[] = qsprintf( $conn_r, 'releephProjectID IN (%Ld)', $this->projectIDs); } $status = $this->status; switch ($status) { case self::STATUS_ALL: break; case self::STATUS_OPEN: $where[] = qsprintf( $conn_r, 'isActive = 1'); break; default: throw new Exception("Unknown status constant '{$status}'!"); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationReleeph'; + } + } diff --git a/src/applications/releeph/query/ReleephProjectQuery.php b/src/applications/releeph/query/ReleephProjectQuery.php index 5a1b09bf4a..2823633499 100644 --- a/src/applications/releeph/query/ReleephProjectQuery.php +++ b/src/applications/releeph/query/ReleephProjectQuery.php @@ -1,130 +1,134 @@ active = $active; return $this; } public function setOrder($order) { $this->order = $order; return $this; } public function withIDs(array $ids) { $this->ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function loadPage() { $table = new ReleephProject(); $conn_r = $table->establishConnection('r'); $rows = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($rows); } public function willFilterPage(array $projects) { assert_instances_of($projects, 'ReleephProject'); $repository_phids = mpull($projects, 'getRepositoryPHID'); $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withPHIDs($repository_phids) ->execute(); $repositories = mpull($repositories, null, 'getPHID'); foreach ($projects as $key => $project) { $repo = idx($repositories, $project->getRepositoryPHID()); if (!$repo) { unset($projects[$key]); continue; } $project->attachRepository($repo); } return $projects; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->active !== null) { $where[] = qsprintf( $conn_r, 'isActive = %d', (int)$this->active); } if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ls)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } protected function getReversePaging() { switch ($this->order) { case self::ORDER_NAME: return true; } return parent::getReversePaging(); } protected function getPagingValue($result) { switch ($this->order) { case self::ORDER_NAME: return $result->getName(); } return parent::getPagingValue(); } protected function getPagingColumn() { switch ($this->order) { case self::ORDER_NAME: return 'name'; case self::ORDER_ID: return parent::getPagingColumn(); default: throw new Exception("Uknown order '{$this->order}'!"); } } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationReleeph'; + } + } diff --git a/src/applications/releeph/query/ReleephRequestQuery.php b/src/applications/releeph/query/ReleephRequestQuery.php index 560f7898fc..b4a9985341 100644 --- a/src/applications/releeph/query/ReleephRequestQuery.php +++ b/src/applications/releeph/query/ReleephRequestQuery.php @@ -1,235 +1,240 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withBranchIDs(array $branch_ids) { $this->branchIDs = $branch_ids; return $this; } public function getRevisionPHID($commit_phid) { if ($this->commitToRevMap) { return idx($this->commitToRevMap, $commit_phid, null); } return null; } public function withStatus($status) { $this->status = $status; return $this; } public function withRequestedCommitPHIDs(array $requested_commit_phids) { $this->requestedCommitPHIDs = $requested_commit_phids; return $this; } public function withRequestorPHIDs(array $phids) { $this->requestorPHIDs = $phids; return $this; } public function withSeverities(array $severities) { $this->severities = $severities; return $this; } public function withRevisionPHIDs(array $revision_phids) { $this->revisionPHIDs = $revision_phids; return $this; } public function loadPage() { $table = new ReleephRequest(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $requests) { // TODO: These should be serviced by the query, but are not currently // denormalized anywhere. For now, filter them here instead. $keep_status = array_fuse($this->getKeepStatusConstants()); if ($keep_status) { foreach ($requests as $key => $request) { if (empty($keep_status[$request->getStatus()])) { unset($requests[$key]); } } } if ($this->severities) { $severities = array_fuse($this->severities); foreach ($requests as $key => $request) { // NOTE: Facebook uses a custom field here. if (ReleephDefaultFieldSelector::isFacebook()) { $severity = $request->getDetail('severity'); } else { $severity = $request->getDetail('releeph:severity'); } if (empty($severities[$severity])) { unset($requests[$key]); } } } return $requests; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->branchIDs) { $where[] = qsprintf( $conn_r, 'branchID IN (%Ld)', $this->branchIDs); } if ($this->requestedCommitPHIDs) { $where[] = qsprintf( $conn_r, 'requestCommitPHID IN (%Ls)', $this->requestedCommitPHIDs); } if ($this->requestorPHIDs) { $where[] = qsprintf( $conn_r, 'requestUserPHID IN (%Ls)', $this->requestorPHIDs); } if ($this->revisionPHIDs) { $type = PhabricatorEdgeConfig::TYPE_DREV_HAS_COMMIT; $edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs($this->revisionPHIDs) ->withEdgeTypes(array($type)) ->execute(); $this->commitToRevMap = array(); foreach ($edges as $revision_phid => $edge) { foreach ($edge[$type] as $commitPHID => $item) { $this->commitToRevMap[$commitPHID] = $revision_phid; } } if (!$this->commitToRevMap) { throw new PhabricatorEmptyQueryException("Malformed Revision Phids"); } $where[] = qsprintf( $conn_r, 'requestCommitPHID IN (%Ls)', array_keys($this->commitToRevMap)); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function getKeepStatusConstants() { switch ($this->status) { case self::STATUS_ALL: return array(); case self::STATUS_OPEN: return array( ReleephRequestStatus::STATUS_REQUESTED, ReleephRequestStatus::STATUS_NEEDS_PICK, ReleephRequestStatus::STATUS_NEEDS_REVERT, ); case self::STATUS_REQUESTED: return array( ReleephRequestStatus::STATUS_REQUESTED, ); case self::STATUS_NEEDS_PULL: return array( ReleephRequestStatus::STATUS_NEEDS_PICK, ); case self::STATUS_REJECTED: return array( ReleephRequestStatus::STATUS_REJECTED, ); case self::STATUS_ABANDONED: return array( ReleephRequestStatus::STATUS_ABANDONED, ); case self::STATUS_PULLED: return array( ReleephRequestStatus::STATUS_PICKED, ); case self::STATUS_NEEDS_REVERT: return array( ReleephRequestStatus::NEEDS_REVERT, ); case self::STATUS_REVERTED: return array( ReleephRequestStatus::REVERTED, ); default: throw new Exception("Unknown status '{$this->status}'!"); } } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationReleeph'; + } + } diff --git a/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeArcanistProject.php b/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeArcanistProject.php index 49f24131a8..e66191fc96 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeArcanistProject.php +++ b/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeArcanistProject.php @@ -1,45 +1,42 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $project = $objects[$phid]; $handle->setName($project->getName()); } } } diff --git a/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeCommit.php b/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeCommit.php index 34f4c4be57..31aab86e95 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeCommit.php +++ b/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeCommit.php @@ -1,87 +1,84 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $commit = $objects[$phid]; $repository = $commit->getRepository(); $callsign = $repository->getCallsign(); $commit_identifier = $commit->getCommitIdentifier(); $name = $repository->formatCommitName($commit_identifier); $summary = $commit->getSummary(); if (strlen($summary)) { $full_name = $name.': '.$summary; } else { $full_name = $name; } $handle->setName($name); $handle->setFullName($full_name); $handle->setURI('/r'.$callsign.$commit_identifier); $handle->setTimestamp($commit->getEpoch()); } } public static function getCommitObjectNamePattern() { $min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH; $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH; return 'r[A-Z]+[1-9]\d*'. '|'. 'r[A-Z]+[a-f0-9]{'.$min_qualified.',40}'. '|'. '[a-f0-9]{'.$min_unqualified.',40}'; } public function canLoadNamedObject($name) { $pattern = self::getCommitObjectNamePattern(); return preg_match('(^'.$pattern.'$)', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $query = id(new DiffusionCommitQuery()) ->setViewer($query->getViewer()) ->withIdentifiers($names); $query->execute(); return $query->getIdentifierMap(); } } diff --git a/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeRepository.php b/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeRepository.php index 70e4c8bf48..1e1cf8f888 100644 --- a/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeRepository.php +++ b/src/applications/repository/phid/PhabricatorRepositoryPHIDTypeRepository.php @@ -1,78 +1,75 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $repository = $objects[$phid]; $callsign = $repository->getCallsign(); $name = $repository->getName(); $handle->setName("r{$callsign}"); $handle->setFullName("r{$callsign} ({$name})"); $handle->setURI("/diffusion/{$callsign}/"); } } public function canLoadNamedObject($name) { return preg_match('/^r[A-Z]+$/', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = substr($name, 1); $id_map[$id][] = $name; } $objects = id(new PhabricatorRepositoryQuery()) ->setViewer($query->getViewer()) ->withCallsigns(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $object) { $callsign = $object->getCallsign(); foreach (idx($id_map, $callsign, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php b/src/applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php index 9e34bda100..13f4cf1820 100644 --- a/src/applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php @@ -1,84 +1,90 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function needRepositories($need_repositories) { $this->needRepositories = $need_repositories; return $this; } protected function loadPage() { $table = new PhabricatorRepositoryArcanistProject(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $projects) { assert_instances_of($projects, 'PhabricatorRepositoryArcanistProject'); if ($this->needRepositories) { $repository_ids = mpull($projects, 'getRepositoryID'); $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withIDs($repository_ids) ->execute(); foreach ($projects as $project) { $repo = idx($repositories, $project->getRepositoryID()); $project->attachRepository($repo); } } return $projects; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + + public function getQueryApplicationClass() { + // TODO: Diffusion? Differential? + return null; + } + } diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 92c374de42..92d8b95adf 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -1,320 +1,325 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withCallsigns(array $callsigns) { $this->callsigns = $callsigns; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withTypes(array $types) { $this->types = $types; return $this; } public function withUUIDs(array $uuids) { $this->uuids = $uuids; return $this; } public function needCommitCounts($need_counts) { $this->needCommitCounts = $need_counts; return $this; } public function needMostRecentCommits($need_commits) { $this->needMostRecentCommits = $need_commits; return $this; } public function setOrder($order) { $this->order = $order; return $this; } protected function loadPage() { $table = new PhabricatorRepository(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T r %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinsClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $repositories = $table->loadAllFromArray($data); if ($this->needCommitCounts) { $sizes = ipull($data, 'size', 'id'); foreach ($repositories as $id => $repository) { $repository->attachCommitCount(nonempty($sizes[$id], 0)); } } if ($this->needMostRecentCommits) { $commit_ids = ipull($data, 'lastCommitID', 'id'); $commit_ids = array_filter($commit_ids); if ($commit_ids) { $commits = id(new DiffusionCommitQuery()) ->setViewer($this->getViewer()) ->withIDs($commit_ids) ->execute(); } else { $commits = array(); } foreach ($repositories as $id => $repository) { $commit = null; if (idx($commit_ids, $id)) { $commit = idx($commits, $commit_ids[$id]); } $repository->attachMostRecentCommit($commit); } } return $repositories; } public function willFilterPage(array $repositories) { assert_instances_of($repositories, 'PhabricatorRepository'); // TODO: Denormalize repository status into the PhabricatorRepository // table so we can do this filtering in the database. foreach ($repositories as $key => $repo) { $status = $this->status; switch ($status) { case self::STATUS_OPEN: if (!$repo->isTracked()) { unset($repositories[$key]); } break; case self::STATUS_CLOSED: if ($repo->isTracked()) { unset($repositories[$key]); } break; case self::STATUS_ALL: break; default: throw new Exception("Unknown status '{$status}'!"); } } return $repositories; } public function getReversePaging() { switch ($this->order) { case self::ORDER_CALLSIGN: case self::ORDER_NAME: return true; } return false; } protected function getPagingColumn() { $order = $this->order; switch ($order) { case self::ORDER_CREATED: return 'r.id'; case self::ORDER_COMMITTED: return 's.epoch'; case self::ORDER_CALLSIGN: return 'r.callsign'; case self::ORDER_NAME: return 'r.name'; default: throw new Exception("Unknown order '{$order}!'"); } } private function loadCursorObject($id) { $query = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getPagingViewer()) ->withIDs(array((int)$id)); if ($this->order == self::ORDER_COMMITTED) { $query->needMostRecentCommits(true); } $results = $query->execute(); return head($results); } protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { $default = parent::buildPagingClause($conn_r); $before_id = $this->getBeforeID(); $after_id = $this->getAfterID(); if (!$before_id && !$after_id) { return $default; } $order = $this->order; if ($order == self::ORDER_CREATED) { return $default; } if ($before_id) { $cursor = $this->loadCursorObject($before_id); } else { $cursor = $this->loadCursorObject($after_id); } if (!$cursor) { return null; } $id_column = array( 'name' => 'r.id', 'type' => 'int', 'value' => $cursor->getID(), ); $columns = array(); switch ($order) { case self::ORDER_COMMITTED: $commit = $cursor->getMostRecentCommit(); if (!$commit) { return null; } $columns[] = array( 'name' => 's.epoch', 'type' => 'int', 'value' => $commit->getEpoch(), ); $columns[] = $id_column; break; case self::ORDER_CALLSIGN: $columns[] = array( 'name' => 'r.callsign', 'type' => 'string', 'value' => $cursor->getCallsign(), 'reverse' => true, ); break; case self::ORDER_NAME: $columns[] = array( 'name' => 'r.name', 'type' => 'string', 'value' => $cursor->getName(), 'reverse' => true, ); $columns[] = $id_column; break; default: throw new Exception("Unknown order '{$order}'!"); } return $this->buildPagingClauseFromMultipleColumns( $conn_r, $columns, array( // TODO: Clean up the column ordering stuff and then make this // depend on getReversePaging(). 'reversed' => (bool)($before_id), )); } private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { $joins = array(); $join_summary_table = $this->needCommitCounts || $this->needMostRecentCommits || ($this->order == self::ORDER_COMMITTED); if ($join_summary_table) { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T s ON r.id = s.repositoryID', PhabricatorRepository::TABLE_SUMMARY); } return implode(' ', $joins); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'r.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'r.phid IN (%Ls)', $this->phids); } if ($this->callsigns) { $where[] = qsprintf( $conn_r, 'r.callsign IN (%Ls)', $this->callsigns); } if ($this->types) { $where[] = qsprintf( $conn_r, 'r.versionControlSystem IN (%Ls)', $this->types); } if ($this->uuids) { $where[] = qsprintf( $conn_r, 'r.uuid IN (%Ls)', $this->uuids); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationDiffusion'; + } + } diff --git a/src/applications/search/query/PhabricatorNamedQueryQuery.php b/src/applications/search/query/PhabricatorNamedQueryQuery.php index 84f9a5eaa8..dccd228a32 100644 --- a/src/applications/search/query/PhabricatorNamedQueryQuery.php +++ b/src/applications/search/query/PhabricatorNamedQueryQuery.php @@ -1,84 +1,90 @@ ids = $ids; return $this; } public function withUserPHIDs(array $user_phids) { $this->userPHIDs = $user_phids; return $this; } public function withEngineClassNames(array $engine_class_names) { $this->engineClassNames = $engine_class_names; return $this; } public function withQueryKeys(array $query_keys) { $this->queryKeys = $query_keys; return $this; } protected function loadPage() { $table = new PhabricatorNamedQuery(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } private function buildWhereClause($conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->engineClassNames) { $where[] = qsprintf( $conn_r, 'engineClassName IN (%Ls)', $this->engineClassNames); } if ($this->userPHIDs) { $where[] = qsprintf( $conn_r, 'userPHID IN (%Ls)', $this->userPHIDs); } if ($this->queryKeys) { $where[] = qsprintf( $conn_r, 'queryKey IN (%Ls)', $this->queryKeys); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationSearch'; + } + } diff --git a/src/applications/search/query/PhabricatorSavedQueryQuery.php b/src/applications/search/query/PhabricatorSavedQueryQuery.php index 929da82a24..70773908a8 100644 --- a/src/applications/search/query/PhabricatorSavedQueryQuery.php +++ b/src/applications/search/query/PhabricatorSavedQueryQuery.php @@ -1,68 +1,74 @@ ids = $ids; return $this; } public function withEngineClassNames(array $engine_class_names) { $this->engineClassNames = $engine_class_names; return $this; } public function withQueryKeys(array $query_keys) { $this->queryKeys = $query_keys; return $this; } protected function loadPage() { $table = new PhabricatorSavedQuery(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } private function buildWhereClause($conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'id IN (%Ld)', $this->ids); } if ($this->engineClassNames) { $where[] = qsprintf( $conn_r, 'engineClassName IN (%Ls)', $this->engineClassNames); } if ($this->queryKeys) { $where[] = qsprintf( $conn_r, 'queryKey IN (%Ls)', $this->queryKeys); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } + + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationSearch'; + } + } diff --git a/src/applications/slowvote/phid/PhabricatorSlowvotePHIDTypePoll.php b/src/applications/slowvote/phid/PhabricatorSlowvotePHIDTypePoll.php index 7f40a8a7ac..36ad531375 100644 --- a/src/applications/slowvote/phid/PhabricatorSlowvotePHIDTypePoll.php +++ b/src/applications/slowvote/phid/PhabricatorSlowvotePHIDTypePoll.php @@ -1,73 +1,70 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $poll = $objects[$phid]; $handle->setName('V'.$poll->getID()); $handle->setFullName('V'.$poll->getID().': '.$poll->getQuestion()); $handle->setURI('/V'.$poll->getID()); } } public function canLoadNamedObject($name) { return preg_match('/^V\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new PhabricatorSlowvoteQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php b/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php index 28963a44bc..8b4677c557 100644 --- a/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php +++ b/src/applications/slowvote/query/PhabricatorSlowvoteQuery.php @@ -1,171 +1,176 @@ ids = $ids; return $this; } public function withPHIDs($phids) { $this->phids = $phids; return $this; } public function withAuthorPHIDs($author_phids) { $this->authorPHIDs = $author_phids; return $this; } public function withVotesByViewer($with_vote) { $this->withVotesByViewer = $with_vote; return $this; } public function needOptions($need_options) { $this->needOptions = $need_options; return $this; } public function needChoices($need_choices) { $this->needChoices = $need_choices; return $this; } public function needViewerChoices($need_viewer_choices) { $this->needViewerChoices = $need_viewer_choices; return $this; } public function loadPage() { $table = new PhabricatorSlowvotePoll(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT p.* FROM %T p %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinsClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } public function willFilterPage(array $polls) { assert_instances_of($polls, 'PhabricatorSlowvotePoll'); $ids = mpull($polls, 'getID'); $viewer = $this->getViewer(); if ($this->needOptions) { $options = id(new PhabricatorSlowvoteOption())->loadAllWhere( 'pollID IN (%Ld)', $ids); $options = mgroup($options, 'getPollID'); foreach ($polls as $poll) { $poll->attachOptions(idx($options, $poll->getID(), array())); } } if ($this->needChoices) { $choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( 'pollID IN (%Ld)', $ids); $choices = mgroup($choices, 'getPollID'); foreach ($polls as $poll) { $poll->attachChoices(idx($choices, $poll->getID(), array())); } // If we need the viewer's choices, we can just fill them from the data // we already loaded. if ($this->needViewerChoices) { foreach ($polls as $poll) { $poll->attachViewerChoices( $viewer, idx( mgroup($poll->getChoices(), 'getAuthorPHID'), $viewer->getPHID(), array())); } } } else if ($this->needViewerChoices) { $choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( 'pollID IN (%Ld) AND authorPHID = %s', $ids, $viewer->getPHID()); $choices = mgroup($choices, 'getPollID'); foreach ($polls as $poll) { $poll->attachViewerChoices( $viewer, idx($choices, $poll->getID(), array())); } } return $polls; } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'p.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'p.phid IN (%Ls)', $this->phids); } if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'p.authorPHID IN (%Ls)', $this->authorPHIDs); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { $joins = array(); if ($this->withVotesByViewer) { $joins[] = qsprintf( $conn_r, 'JOIN %T vv ON vv.pollID = p.id AND vv.authorPHID = %s', id(new PhabricatorSlowvoteChoice())->getTableName(), $this->getViewer()->getPHID()); } return implode(' ', $joins); } protected function getPagingColumn() { return 'p.id'; } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationSlowvote'; + } + } diff --git a/src/applications/tokens/phid/PhabricatorTokenPHIDTypeToken.php b/src/applications/tokens/phid/PhabricatorTokenPHIDTypeToken.php index 7d0a1f69a6..c641a1efbd 100644 --- a/src/applications/tokens/phid/PhabricatorTokenPHIDTypeToken.php +++ b/src/applications/tokens/phid/PhabricatorTokenPHIDTypeToken.php @@ -1,44 +1,41 @@ setViewer($query->getViewer()) - ->setParentQuery($query) - ->withPHIDs($phids) - ->execute(); + ->withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $token = $objects[$phid]; $name = $token->getName(); $handle->setName("{$name} Token"); } } } diff --git a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php index 27a2c959dd..ea835f47cc 100644 --- a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php @@ -1,92 +1,96 @@ tokenPHIDs = $token_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; } protected function loadPage() { $table = new PhabricatorTokenGiven(); $conn_r = $table->establishConnection('r'); $rows = queryfx_all( $conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($rows); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->authorPHIDs) { $where[] = qsprintf( $conn_r, 'authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->objectPHIDs) { $where[] = qsprintf( $conn_r, 'objectPHID IN (%Ls)', $this->objectPHIDs); } if ($this->tokenPHIDs) { $where[] = qsprintf( $conn_r, 'tokenPHID IN (%Ls)', $this->tokenPHIDs); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } public function willFilterPage(array $results) { $object_phids = array_filter(mpull($results, 'getObjectPHID')); if (!$object_phids) { return array(); } $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($object_phids) ->execute(); foreach ($results as $key => $result) { $phid = $result->getObjectPHID(); if (empty($objects[$phid])) { unset($results[$key]); } else { $result->attachObject($objects[$phid]); } } return $results; } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationTokens'; + } + } diff --git a/src/applications/tokens/query/PhabricatorTokenQuery.php b/src/applications/tokens/query/PhabricatorTokenQuery.php index c4bf4a975b..1fa5d347a8 100644 --- a/src/applications/tokens/query/PhabricatorTokenQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenQuery.php @@ -1,64 +1,69 @@ phids = $phids; return $this; } protected function loadPage() { $tokens = $this->getBuiltinTokens(); if ($this->phids) { $map = array_fill_keys($this->phids, true); foreach ($tokens as $key => $token) { if (empty($map[$token->getPHID()])) { unset($tokens[$key]); } } } return $tokens; } private function getBuiltinTokens() { $specs = array( array('like-1', pht('Like')), array('like-2', pht('Dislike')), array('heart-1', pht('Love')), array('heart-2', pht('Heartbreak')), array('medal-1', pht('Orange Medal')), array('medal-2', pht('Grey Medal')), array('medal-3', pht('Yellow Medal')), array('medal-4', pht('Manufacturing Defect?')), array('coin-1', pht('Haypence')), array('coin-2', pht('Piece of Eight')), array('coin-3', pht('Doubloon')), array('coin-4', pht('Mountain of Wealth')), array('misc-1', pht('Pterodactyl')), array('misc-2', pht('Evil Spooky Haunted Tree')), array('misc-3', pht('Baby Tequila')), array('misc-4', pht('The World Burns')), ); $type = PhabricatorTokenPHIDTypeToken::TYPECONST; $tokens = array(); foreach ($specs as $id => $spec) { list($image, $name) = $spec; $token = id(new PhabricatorToken()) ->setID($id) ->setName($name) ->setPHID('PHID-'.$type.'-'.$image); $tokens[] = $token; } return $tokens; } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationTokens'; + } + } diff --git a/src/applications/tokens/query/PhabricatorTokenReceiverQuery.php b/src/applications/tokens/query/PhabricatorTokenReceiverQuery.php index bfa784fa41..6485416eb2 100644 --- a/src/applications/tokens/query/PhabricatorTokenReceiverQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenReceiverQuery.php @@ -1,37 +1,41 @@ establishConnection('r'); $rows = queryfx_all( $conn_r, 'SELECT objectPHID, tokenCount FROM %T ORDER BY tokenCount DESC', $table->getTableName()); $this->tokenCounts = ipull($rows, 'tokenCount', 'objectPHID'); return ipull($rows, 'objectPHID'); } public function willFilterPage(array $phids) { $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($phids) ->execute(); // Reorder the objects in the input order. $objects = array_select_keys($objects, $phids); return $objects; } public function getTokenCounts() { return $this->tokenCounts; } + public function getQueryApplicationClass() { + return 'PhabricatorApplicationTokens'; + } + } diff --git a/src/applications/transactions/phid/PhabricatorApplicationTransactionPHIDTypeTransaction.php b/src/applications/transactions/phid/PhabricatorApplicationTransactionPHIDTypeTransaction.php index d818dad226..97841ba04f 100644 --- a/src/applications/transactions/phid/PhabricatorApplicationTransactionPHIDTypeTransaction.php +++ b/src/applications/transactions/phid/PhabricatorApplicationTransactionPHIDTypeTransaction.php @@ -1,80 +1,92 @@ setAncestorClass('PhabricatorApplicationTransactionQuery') ->loadObjects(); $queries = array(); foreach ($objects as $object) { $type = $object ->getTemplateApplicationTransaction() ->getApplicationTransactionType(); $queries[$type] = $object; } } $phid_subtypes = array(); foreach ($phids as $phid) { $subtype = phid_get_subtype($phid); if ($subtype) { $phid_subtypes[$subtype][] = $phid; } } $results = array(); foreach ($phid_subtypes as $subtype => $subtype_phids) { $query = idx($queries, $subtype); if (!$query) { continue; } - $xactions = id(clone $query) + $xaction_query = id(clone $query) ->setViewer($object_query->getViewer()) ->setParentQuery($object_query) - ->withPHIDs($subtype_phids) - ->execute(); + ->withPHIDs($subtype_phids); + + if (!$xaction_query->canViewerUseQueryApplication()) { + $object_query->addPolicyFilteredPHIDs(array_fuse($subtype_phids)); + continue; + } + + $xactions = $xaction_query->execute(); $results += mpull($xactions, null, 'getPHID'); } return $results; } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { // NOTE: We don't produce meaningful handles here because they're // impractical to produce and no application uses them. } } diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php index 6cb16c7e50..038fadb9cb 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php @@ -1,62 +1,68 @@ template = $template; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withTransactionPHIDs(array $transaction_phids) { $this->transactionPHIDs = $transaction_phids; return $this; } protected function loadPage() { $table = $this->template; $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T xc %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); return $table->loadAllFromArray($data); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->phids) { $where[] = qsprintf( $conn_r, 'phid IN (%Ls)', $this->phids); } if ($this->transactionPHIDs) { $where[] = qsprintf( $conn_r, 'transactionPHID IN (%Ls)', $this->transactionPHIDs); } return $this->formatWhereClause($where); } + public function getQueryApplicationClass() { + // TODO: Figure out the app via the template? + return null; + } + + } diff --git a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php index 00523cffe4..1ef212d7e5 100644 --- a/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php +++ b/src/applications/transactions/query/PhabricatorApplicationTransactionQuery.php @@ -1,163 +1,169 @@ 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 PhabricatorApplicationTransactionCommentQuery()) ->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); } } 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(); } foreach ($xactions as $xaction) { $xaction->setHandles( array_select_keys( $handles, $phids[$xaction->getPHID()])); } } return $xactions; } private 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/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php index 20b45df42d..55c6edc277 100644 --- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php +++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php @@ -1,584 +1,628 @@ setViewer($user) * ->withConstraint($example) * ->execute(); * * Normally, you should extend @{class:PhabricatorCursorPagedPolicyAwareQuery}, * not this class. @{class:PhabricatorCursorPagedPolicyAwareQuery} provides a * more practical interface for building usable queries against most object * types. * * NOTE: Although this class extends @{class:PhabricatorOffsetPagedQuery}, * offset paging with policy filtering is not efficient. All results must be * loaded into the application and filtered here: skipping `N` rows via offset * is an `O(N)` operation with a large constant. Prefer cursor-based paging * with @{class:PhabricatorCursorPagedPolicyAwareQuery}, which can filter far * more efficiently in MySQL. * * @task config Query Configuration * @task exec Executing Queries * @task policyimpl Policy Query Implementation */ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery { private $viewer; private $raisePolicyExceptions; private $parentQuery; private $rawResultLimit; private $capabilities; private $workspace = array(); private $policyFilteredPHIDs = array(); + private $canUseApplication; /* -( Query Configuration )------------------------------------------------ */ /** * Set the viewer who is executing the query. Results will be filtered * according to the viewer's capabilities. You must set a viewer to execute * a policy query. * * @param PhabricatorUser The viewing user. * @return this * @task config */ final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } /** * Get the query's viewer. * * @return PhabricatorUser The viewing user. * @task config */ final public function getViewer() { return $this->viewer; } /** * Set the parent query of this query. This is useful for nested queries so * that configuration like whether or not to raise policy exceptions is * seamlessly passed along to child queries. * * @return this * @task config */ final public function setParentQuery(PhabricatorPolicyAwareQuery $query) { $this->parentQuery = $query; return $this; } /** * Get the parent query. See @{method:setParentQuery} for discussion. * * @return PhabricatorPolicyAwareQuery The parent query. * @task config */ final public function getParentQuery() { return $this->parentQuery; } /** * Hook to configure whether this query should raise policy exceptions. * * @return this * @task config */ final public function setRaisePolicyExceptions($bool) { $this->raisePolicyExceptions = $bool; return $this; } /** * @return bool * @task config */ final public function shouldRaisePolicyExceptions() { return (bool) $this->raisePolicyExceptions; } /** * @task config */ final public function requireCapabilities(array $capabilities) { $this->capabilities = $capabilities; return $this; } /* -( Query Execution )---------------------------------------------------- */ /** * Execute the query, expecting a single result. This method simplifies * loading objects for detail pages or edit views. * * // Load one result by ID. * $obj = id(new ExampleQuery()) * ->setViewer($user) * ->withIDs(array($id)) * ->executeOne(); * if (!$obj) { * return new Aphront404Response(); * } * * If zero results match the query, this method returns `null`. * If one result matches the query, this method returns that result. * * If two or more results match the query, this method throws an exception. * You should use this method only when the query constraints guarantee at * most one match (e.g., selecting a specific ID or PHID). * * If one result matches the query but it is caught by the policy filter (for * example, the user is trying to view or edit an object which exists but * which they do not have permission to see) a policy exception is thrown. * * @return mixed Single result, or null. * @task exec */ final public function executeOne() { $this->setRaisePolicyExceptions(true); try { $results = $this->execute(); } catch (Exception $ex) { $this->setRaisePolicyExceptions(false); throw $ex; } if (count($results) > 1) { throw new Exception("Expected a single result!"); } if (!$results) { return null; } return head($results); } /** * Execute the query, loading all visible results. * * @return list Result objects. * @task exec */ final public function execute() { if (!$this->viewer) { throw new Exception("Call setViewer() before execute()!"); } $parent_query = $this->getParentQuery(); if ($parent_query) { $this->setRaisePolicyExceptions( $parent_query->shouldRaisePolicyExceptions()); } $results = array(); $filter = $this->getPolicyFilter(); $offset = (int)$this->getOffset(); $limit = (int)$this->getLimit(); $count = 0; if ($limit) { $need = $offset + $limit; } else { $need = 0; } $this->willExecute(); do { if ($need) { $this->rawResultLimit = min($need - $count, 1024); } else { $this->rawResultLimit = 0; } - try { - $page = $this->loadPage(); - } catch (PhabricatorEmptyQueryException $ex) { + if ($this->canViewerUseQueryApplication()) { + try { + $page = $this->loadPage(); + } catch (PhabricatorEmptyQueryException $ex) { + $page = array(); + } + } else { $page = array(); } if ($page) { $maybe_visible = $this->willFilterPage($page); } else { $maybe_visible = array(); } if ($this->shouldDisablePolicyFiltering()) { $visible = $maybe_visible; } else { $visible = $filter->apply($maybe_visible); $policy_filtered = array(); foreach ($maybe_visible as $key => $object) { if (empty($visible[$key])) { $phid = $object->getPHID(); if ($phid) { $policy_filtered[$phid] = $phid; } } } $this->addPolicyFilteredPHIDs($policy_filtered); } if ($visible) { $this->putObjectsInWorkspace($this->getWorkspaceMapForPage($visible)); $visible = $this->didFilterPage($visible); } $removed = array(); foreach ($maybe_visible as $key => $object) { if (empty($visible[$key])) { $removed[$key] = $object; } } $this->didFilterResults($removed); foreach ($visible as $key => $result) { ++$count; // If we have an offset, we just ignore that many results and start // storing them only once we've hit the offset. This reduces memory // requirements for large offsets, compared to storing them all and // slicing them away later. if ($count > $offset) { $results[$key] = $result; } if ($need && ($count >= $need)) { // If we have all the rows we need, break out of the paging query. break 2; } } if (!$this->rawResultLimit) { // If we don't have a load count, we loaded all the results. We do // not need to load another page. break; } if (count($page) < $this->rawResultLimit) { // If we have a load count but the unfiltered results contained fewer // objects, we know this was the last page of objects; we do not need // to load another page because we can deduce it would be empty. break; } $this->nextPage($page); } while (true); $results = $this->didLoadResults($results); return $results; } private function getPolicyFilter() { $filter = new PhabricatorPolicyFilter(); $filter->setViewer($this->viewer); if (!$this->capabilities) { $capabilities = array( PhabricatorPolicyCapability::CAN_VIEW, ); } else { $capabilities = $this->capabilities; } $filter->requireCapabilities($capabilities); $filter->raisePolicyExceptions($this->shouldRaisePolicyExceptions()); return $filter; } protected function didRejectResult(PhabricatorPolicyInterface $object) { $this->getPolicyFilter()->rejectObject( $object, $object->getPolicy(PhabricatorPolicyCapability::CAN_VIEW), PhabricatorPolicyCapability::CAN_VIEW); } - protected function addPolicyFilteredPHIDs(array $phids) { + public function addPolicyFilteredPHIDs(array $phids) { $this->policyFilteredPHIDs += $phids; if ($this->getParentQuery()) { $this->getParentQuery()->addPolicyFilteredPHIDs($phids); } return $this; } /** * Return a map of all object PHIDs which were loaded in the query but * filtered out by policy constraints. This allows a caller to distinguish * between objects which do not exist (or, at least, were filtered at the * content level) and objects which exist but aren't visible. * * @return map Map of object PHIDs which were filtered * by policies. * @task exec */ public function getPolicyFilteredPHIDs() { return $this->policyFilteredPHIDs; } /* -( Query Workspace )---------------------------------------------------- */ /** * Put a map of objects into the query workspace. Many queries perform * subqueries, which can eventually end up loading the same objects more than * once (often to perform policy checks). * * For example, loading a user may load the user's profile image, which might * load the user object again in order to verify that the viewer has * permission to see the file. * * The "query workspace" allows queries to load objects from elsewhere in a * query block instead of refetching them. * * When using the query workspace, it's important to obey two rules: * * **Never put objects into the workspace which the viewer may not be able * to see**. You need to apply all policy filtering //before// putting * objects in the workspace. Otherwise, subqueries may read the objects and * use them to permit access to content the user shouldn't be able to view. * * **Fully enrich objects pulled from the workspace.** After pulling objects * from the workspace, you still need to load and attach any additional * content the query requests. Otherwise, a query might return objects without * requested content. * * Generally, you do not need to update the workspace yourself: it is * automatically populated as a side effect of objects surviving policy * filtering. * * @param map Objects to add to the query * workspace. * @return this * @task workspace */ public function putObjectsInWorkspace(array $objects) { assert_instances_of($objects, 'PhabricatorPolicyInterface'); $viewer_phid = $this->getViewer()->getPHID(); // The workspace is scoped per viewer to prevent accidental contamination. if (empty($this->workspace[$viewer_phid])) { $this->workspace[$viewer_phid] = array(); } $this->workspace[$viewer_phid] += $objects; return $this; } /** * Retrieve objects from the query workspace. For more discussion about the * workspace mechanism, see @{method:putObjectsInWorkspace}. This method * searches both the current query's workspace and the workspaces of parent * queries. * * @param list List of PHIDs to retreive. * @return this * @task workspace */ public function getObjectsFromWorkspace(array $phids) { $viewer_phid = $this->getViewer()->getPHID(); $results = array(); foreach ($phids as $key => $phid) { if (isset($this->workspace[$viewer_phid][$phid])) { $results[$phid] = $this->workspace[$viewer_phid][$phid]; unset($phids[$key]); } } if ($phids && $this->getParentQuery()) { $results += $this->getParentQuery()->getObjectsFromWorkspace($phids); } return $results; } /** * Convert a result page to a `` map. * * @param list Objects. * @return map Map of objects which can * be put into the workspace. * @task workspace */ protected function getWorkspaceMapForPage(array $results) { $map = array(); foreach ($results as $result) { $phid = $result->getPHID(); if ($phid !== null) { $map[$phid] = $result; } } return $map; } /* -( Policy Query Implementation )---------------------------------------- */ /** * Get the number of results @{method:loadPage} should load. If the value is * 0, @{method:loadPage} should load all available results. * * @return int The number of results to load, or 0 for all results. * @task policyimpl */ final protected function getRawResultLimit() { return $this->rawResultLimit; } /** * Hook invoked before query execution. Generally, implementations should * reset any internal cursors. * * @return void * @task policyimpl */ protected function willExecute() { return; } /** * Load a raw page of results. Generally, implementations should load objects * from the database. They should attempt to return the number of results * hinted by @{method:getRawResultLimit}. * * @return list List of filterable policy objects. * @task policyimpl */ abstract protected function loadPage(); /** * Update internal state so that the next call to @{method:loadPage} will * return new results. Generally, you should adjust a cursor position based * on the provided result page. * * @param list The current page of results. * @return void * @task policyimpl */ abstract protected function nextPage(array $page); /** * Hook for applying a page filter prior to the privacy filter. This allows * you to drop some items from the result set without creating problems with * pagination or cursor updates. You can also load and attach data which is * required to perform policy filtering. * * Generally, you should load non-policy data and perform non-policy filtering * later, in @{method:didFilterPage}. Strictly fewer objects will make it that * far (so the program will load less data) and subqueries from that context * can use the query workspace to further reduce query load. * * This method will only be called if data is available. Implementations * do not need to handle the case of no results specially. * * @param list Results from `loadPage()`. * @return list Objects for policy filtering. * @task policyimpl */ protected function willFilterPage(array $page) { return $page; } /** * Hook for performing additional non-policy loading or filtering after an * object has satisfied all policy checks. Generally, this means loading and * attaching related data. * * Subqueries executed during this phase can use the query workspace, which * may improve performance or make circular policies resolvable. Data which * is not necessary for policy filtering should generally be loaded here. * * This callback can still filter objects (for example, if attachable data * is discovered to not exist), but should not do so for policy reasons. * * This method will only be called if data is available. Implementations do * not need to handle the case of no results specially. * * @param list Results from @{method:willFilterPage()}. * @return list Objects after additional * non-policy processing. */ protected function didFilterPage(array $page) { return $page; } /** * Hook for removing filtered results from alternate result sets. This * hook will be called with any objects which were returned by the query but * filtered for policy reasons. The query should remove them from any cached * or partial result sets. * * @param list List of objects that should not be returned by alternate * result mechanisms. * @return void * @task policyimpl */ protected function didFilterResults(array $results) { return; } /** * Hook for applying final adjustments before results are returned. This is * used by @{class:PhabricatorCursorPagedPolicyAwareQuery} to reverse results * that are queried during reverse paging. * * @param list Query results. * @return list Final results. * @task policyimpl */ protected function didLoadResults(array $results) { return $results; } /** * Allows a subclass to disable policy filtering. This method is dangerous. * It should be used only if the query loads data which has already been * filtered (for example, because it wraps some other query which uses * normal policy filtering). * * @return bool True to disable all policy filtering. * @task policyimpl */ protected function shouldDisablePolicyFiltering() { return false; } + + /** + * If this query belongs to an application, return the application class name + * here. This will prevent the query from returning results if the viewer can + * not access the application. + * + * If this query does not belong to an application, return `null`. + * + * @return string|null Application class name. + */ + abstract public function getQueryApplicationClass(); + + + /** + * Determine if the viewer has permission to use this query's application. + * For queries which aren't part of an application, this method always returns + * true. + * + * @return bool True if the viewer has application-level permission to + * execute the query. + */ + public function canViewerUseQueryApplication() { + if ($this->canUseApplication === null) { + $class = $this->getQueryApplicationClass(); + if (!$class) { + $this->canUseApplication = true; + } else { + $result = id(new PhabricatorApplicationQuery()) + ->setViewer($this->getViewer()) + ->withClasses(array($class)) + ->execute(); + + $this->canUseApplication = (bool)$result; + } + } + + return $this->canUseApplication; + } + }