Remove product literal strings in "pht()", part 18
[phabricator.git] / src / applications / feed / query / PhabricatorFeedTransactionQuery.php
blobd0a9f53e355d111ad1ab494d01a5f2e8626c0514
1 <?php
3 final class PhabricatorFeedTransactionQuery
4 extends PhabricatorCursorPagedPolicyAwareQuery {
6 private $phids;
7 private $authorPHIDs;
8 private $objectTypes;
9 private $createdMin;
10 private $createdMax;
12 public function withPHIDs(array $phids) {
13 $this->phids = $phids;
14 return $this;
17 public function withAuthorPHIDs(array $phids) {
18 $this->authorPHIDs = $phids;
19 return $this;
22 public function withObjectTypes(array $types) {
23 $this->objectTypes = $types;
24 return $this;
27 public function withDateCreatedBetween($min, $max) {
28 $this->createdMin = $min;
29 $this->createdMax = $max;
30 return $this;
33 public function newResultObject() {
34 // Return an arbitrary valid transaction object. The actual query may
35 // return objects of any subclass of "ApplicationTransaction" when it is
36 // executed, but we need to pick something concrete here to make some
37 // integrations work (like automatic handling of PHIDs in data export).
38 return new PhabricatorUserTransaction();
41 protected function loadPage() {
42 $queries = $this->newTransactionQueries();
44 $xactions = array();
46 if ($this->shouldLimitResults()) {
47 $limit = $this->getRawResultLimit();
48 if (!$limit) {
49 $limit = null;
51 } else {
52 $limit = null;
55 // We're doing a bit of manual work to get paging working, because this
56 // query aggregates the results of a large number of subqueries.
58 // Overall, we're ordering transactions by "<dateCreated, phid>". Ordering
59 // by PHID is not very meaningful, but we don't need the ordering to be
60 // especially meaningful, just consistent. Using PHIDs is easy and does
61 // everything we need it to technically.
63 // To actually configure paging, if we have an external cursor, we load
64 // the internal cursor first. Then we pass it to each subquery and the
65 // subqueries pretend they just loaded a page where it was the last object.
66 // This configures their queries properly and we can aggregate a cohesive
67 // set of results by combining all the queries.
69 $cursor = $this->getExternalCursorString();
70 if ($cursor !== null) {
71 $cursor_object = $this->newInternalCursorFromExternalCursor($cursor);
72 } else {
73 $cursor_object = null;
76 $is_reversed = $this->getIsQueryOrderReversed();
78 $created_min = $this->createdMin;
79 $created_max = $this->createdMax;
81 $xaction_phids = $this->phids;
82 $author_phids = $this->authorPHIDs;
84 foreach ($queries as $query) {
85 $query->withDateCreatedBetween($created_min, $created_max);
87 if ($xaction_phids !== null) {
88 $query->withPHIDs($xaction_phids);
91 if ($author_phids !== null) {
92 $query->withAuthorPHIDs($author_phids);
95 if ($limit !== null) {
96 $query->setLimit($limit);
99 if ($cursor_object !== null) {
100 $query
101 ->setAggregatePagingCursor($cursor_object)
102 ->setIsQueryOrderReversed($is_reversed);
105 $query->setOrder('global');
107 $query_xactions = $query->execute();
108 foreach ($query_xactions as $query_xaction) {
109 $xactions[] = $query_xaction;
112 $xactions = msortv($xactions, 'newGlobalSortVector');
113 if ($is_reversed) {
114 $xactions = array_reverse($xactions);
117 if ($limit !== null) {
118 $xactions = array_slice($xactions, 0, $limit);
120 // If we've found enough transactions to fill up the entire requested
121 // page size, we can narrow the search window: transactions after the
122 // last transaction we've found so far can't possibly be part of the
123 // result set.
125 if (count($xactions) === $limit) {
126 $last_date = last($xactions)->getDateCreated();
127 if ($is_reversed) {
128 if ($created_max === null) {
129 $created_max = $last_date;
130 } else {
131 $created_max = min($created_max, $last_date);
133 } else {
134 if ($created_min === null) {
135 $created_min = $last_date;
136 } else {
137 $created_min = max($created_min, $last_date);
144 return $xactions;
147 public function getQueryApplicationClass() {
148 return 'PhabricatorFeedApplication';
151 private function newTransactionQueries() {
152 $viewer = $this->getViewer();
154 $queries = id(new PhutilClassMapQuery())
155 ->setAncestorClass('PhabricatorApplicationTransactionQuery')
156 ->execute();
158 $type_map = array();
160 // If we're querying for specific transaction PHIDs, we only need to
161 // consider queries which may load transactions with subtypes present
162 // in the list.
164 // For example, if we're loading Maniphest Task transaction PHIDs, we know
165 // we only have to look at Maniphest Task transactions, since other types
166 // of objects will never have the right transaction PHIDs.
168 $xaction_phids = $this->phids;
169 if ($xaction_phids) {
170 foreach ($xaction_phids as $xaction_phid) {
171 $type_map[phid_get_subtype($xaction_phid)] = true;
175 $object_types = $this->objectTypes;
176 if ($object_types) {
177 $object_types = array_fuse($object_types);
180 $results = array();
181 foreach ($queries as $query) {
182 $query_type = $query->getTemplateApplicationTransaction()
183 ->getApplicationTransactionType();
185 if ($type_map) {
186 if (!isset($type_map[$query_type])) {
187 continue;
191 if ($object_types) {
192 if (!isset($object_types[$query_type])) {
193 continue;
197 $results[] = id(clone $query)
198 ->setViewer($viewer)
199 ->setParentQuery($this);
202 return $results;
205 protected function newExternalCursorStringForResult($object) {
206 return (string)$object->getPHID();
209 protected function applyExternalCursorConstraintsToQuery(
210 PhabricatorCursorPagedPolicyAwareQuery $subquery,
211 $cursor) {
212 $subquery->withPHIDs(array($cursor));