@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3/**
4 * @extends PhabricatorCursorPagedPolicyAwareQuery<ConpherenceThread>
5 */
6final class ConpherenceThreadQuery
7 extends PhabricatorCursorPagedPolicyAwareQuery {
8
9 const TRANSACTION_LIMIT = 100;
10
11 private $phids;
12 private $ids;
13 private $participantPHIDs;
14 private $needParticipants;
15 private $needTransactions;
16 private $afterTransactionID;
17 private $beforeTransactionID;
18 private $transactionLimit;
19 private $fulltext;
20 private $needProfileImage;
21
22 public function needParticipants($need) {
23 $this->needParticipants = $need;
24 return $this;
25 }
26
27 public function needProfileImage($need) {
28 $this->needProfileImage = $need;
29 return $this;
30 }
31
32 public function needTransactions($need_transactions) {
33 $this->needTransactions = $need_transactions;
34 return $this;
35 }
36
37 public function withIDs(array $ids) {
38 $this->ids = $ids;
39 return $this;
40 }
41
42 public function withPHIDs(array $phids) {
43 $this->phids = $phids;
44 return $this;
45 }
46
47 public function withParticipantPHIDs(array $phids) {
48 $this->participantPHIDs = $phids;
49 return $this;
50 }
51
52 public function setAfterTransactionID($id) {
53 $this->afterTransactionID = $id;
54 return $this;
55 }
56
57 public function setBeforeTransactionID($id) {
58 $this->beforeTransactionID = $id;
59 return $this;
60 }
61
62 public function setTransactionLimit($transaction_limit) {
63 $this->transactionLimit = $transaction_limit;
64 return $this;
65 }
66
67 public function getTransactionLimit() {
68 return $this->transactionLimit;
69 }
70
71 public function withFulltext($query) {
72 $this->fulltext = $query;
73 return $this;
74 }
75
76 public function withTitleNgrams($ngrams) {
77 return $this->withNgramsConstraint(
78 id(new ConpherenceThreadTitleNgrams()),
79 $ngrams);
80 }
81
82 protected function loadPage() {
83 $table = new ConpherenceThread();
84 $conn_r = $table->establishConnection('r');
85
86 $data = queryfx_all(
87 $conn_r,
88 'SELECT thread.* FROM %T thread %Q %Q %Q %Q %Q',
89 $table->getTableName(),
90 $this->buildJoinClause($conn_r),
91 $this->buildWhereClause($conn_r),
92 $this->buildGroupClause($conn_r),
93 $this->buildOrderClause($conn_r),
94 $this->buildLimitClause($conn_r));
95
96 $conpherences = $table->loadAllFromArray($data);
97
98 if ($conpherences) {
99 $conpherences = mpull($conpherences, null, 'getPHID');
100 $this->loadParticipantsAndInitHandles($conpherences);
101 if ($this->needParticipants) {
102 $this->loadCoreHandles($conpherences, 'getParticipantPHIDs');
103 }
104 if ($this->needTransactions) {
105 $this->loadTransactionsAndHandles($conpherences);
106 }
107 if ($this->needProfileImage) {
108 $default = null;
109 $file_phids = mpull($conpherences, 'getProfileImagePHID');
110 $file_phids = array_filter($file_phids);
111 if ($file_phids) {
112 $files = id(new PhabricatorFileQuery())
113 ->setParentQuery($this)
114 ->setViewer($this->getViewer())
115 ->withPHIDs($file_phids)
116 ->execute();
117 $files = mpull($files, null, 'getPHID');
118 } else {
119 $files = array();
120 }
121
122 foreach ($conpherences as $conpherence) {
123 $file = idx($files, $conpherence->getProfileImagePHID());
124 if (!$file) {
125 if (!$default) {
126 $default = PhabricatorFile::loadBuiltin(
127 $this->getViewer(),
128 'conpherence.png');
129 }
130 $file = $default;
131 }
132 $conpherence->attachProfileImageFile($file);
133 }
134 }
135 }
136
137 return $conpherences;
138 }
139
140 protected function buildGroupClause(AphrontDatabaseConnection $conn_r) {
141 if ($this->participantPHIDs !== null ||
142 phutil_nonempty_string($this->fulltext)) {
143 return qsprintf($conn_r, 'GROUP BY thread.id');
144 } else {
145 return $this->buildApplicationSearchGroupClause($conn_r);
146 }
147 }
148
149 protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
150 $joins = parent::buildJoinClauseParts($conn);
151
152 if ($this->participantPHIDs !== null) {
153 $joins[] = qsprintf(
154 $conn,
155 'JOIN %T p ON p.conpherencePHID = thread.phid',
156 id(new ConpherenceParticipant())->getTableName());
157 }
158
159 if (phutil_nonempty_string($this->fulltext)) {
160 $joins[] = qsprintf(
161 $conn,
162 'JOIN %T idx ON idx.threadPHID = thread.phid',
163 id(new ConpherenceIndex())->getTableName());
164 }
165
166 // See note in buildWhereClauseParts() about this optimization.
167 $viewer = $this->getViewer();
168 if (!$viewer->isOmnipotent() && $viewer->isLoggedIn()) {
169 $joins[] = qsprintf(
170 $conn,
171 'LEFT JOIN %T vp ON vp.conpherencePHID = thread.phid
172 AND vp.participantPHID = %s',
173 id(new ConpherenceParticipant())->getTableName(),
174 $viewer->getPHID());
175 }
176
177 return $joins;
178 }
179
180 protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
181 $where = parent::buildWhereClauseParts($conn);
182
183 // Optimize policy filtering of private rooms. If we are not looking for
184 // particular rooms by ID or PHID, we can just skip over any rooms with
185 // "View Policy: Room Participants" if the viewer isn't a participant: we
186 // know they won't be able to see the room.
187 // This avoids overheating browse/search queries, since it's common for
188 // a large number of rooms to be private and have this view policy.
189 $viewer = $this->getViewer();
190
191 $can_optimize =
192 !$viewer->isOmnipotent() &&
193 ($this->ids === null) &&
194 ($this->phids === null);
195
196 if ($can_optimize) {
197 $members_policy = id(new ConpherenceThreadMembersPolicyRule())
198 ->getObjectPolicyFullKey();
199 $policies = array(
200 $members_policy,
201 PhabricatorPolicies::POLICY_USER,
202 PhabricatorPolicies::POLICY_ADMIN,
203 PhabricatorPolicies::POLICY_NOONE,
204 );
205
206 if ($viewer->isLoggedIn()) {
207 $where[] = qsprintf(
208 $conn,
209 'thread.viewPolicy NOT IN (%Ls) OR vp.participantPHID = %s',
210 $policies,
211 $viewer->getPHID());
212 } else {
213 $where[] = qsprintf(
214 $conn,
215 'thread.viewPolicy NOT IN (%Ls)',
216 $policies);
217 }
218 }
219
220 if ($this->ids !== null) {
221 $where[] = qsprintf(
222 $conn,
223 'thread.id IN (%Ld)',
224 $this->ids);
225 }
226
227 if ($this->phids !== null) {
228 $where[] = qsprintf(
229 $conn,
230 'thread.phid IN (%Ls)',
231 $this->phids);
232 }
233
234 if ($this->participantPHIDs !== null) {
235 $where[] = qsprintf(
236 $conn,
237 'p.participantPHID IN (%Ls)',
238 $this->participantPHIDs);
239 }
240
241 if (phutil_nonempty_string($this->fulltext)) {
242 $where[] = qsprintf(
243 $conn,
244 'MATCH(idx.corpus) AGAINST (%s IN BOOLEAN MODE)',
245 $this->fulltext);
246 }
247
248 return $where;
249 }
250
251 private function loadParticipantsAndInitHandles(array $conpherences) {
252 $participants = id(new ConpherenceParticipant())
253 ->loadAllWhere('conpherencePHID IN (%Ls)', array_keys($conpherences));
254 $map = mgroup($participants, 'getConpherencePHID');
255
256 foreach ($conpherences as $current_conpherence) {
257 $conpherence_phid = $current_conpherence->getPHID();
258
259 $conpherence_participants = idx(
260 $map,
261 $conpherence_phid,
262 array());
263
264 $conpherence_participants = mpull(
265 $conpherence_participants,
266 null,
267 'getParticipantPHID');
268
269 $current_conpherence->attachParticipants($conpherence_participants);
270 $current_conpherence->attachHandles(array());
271 }
272
273 return $this;
274 }
275
276 private function loadCoreHandles(
277 array $conpherences,
278 $method) {
279
280 $handle_phids = array();
281 foreach ($conpherences as $conpherence) {
282 $handle_phids[$conpherence->getPHID()] =
283 $conpherence->$method();
284 }
285 $flat_phids = array_mergev($handle_phids);
286 $viewer = $this->getViewer();
287 $handles = $viewer->loadHandles($flat_phids);
288 $handles = iterator_to_array($handles);
289 foreach ($handle_phids as $conpherence_phid => $phids) {
290 $conpherence = $conpherences[$conpherence_phid];
291 $conpherence->attachHandles(
292 $conpherence->getHandles() + array_select_keys($handles, $phids));
293 }
294 return $this;
295 }
296
297 private function loadTransactionsAndHandles(array $conpherences) {
298 // NOTE: This is older code which has been modernized to the minimum
299 // standard required by T13266. It probably isn't the best available
300 // approach to the problems it solves.
301
302 $limit = $this->getTransactionLimit();
303 if ($limit) {
304 // fetch an extra for "show older" scenarios
305 $limit = $limit + 1;
306 } else {
307 $limit = 0xFFFF;
308 }
309
310 $pager = id(new AphrontCursorPagerView())
311 ->setPageSize($limit);
312
313 // We have to flip these for the underlying query class. The semantics of
314 // paging are tricky business.
315 if ($this->afterTransactionID) {
316 $pager->setBeforeID($this->afterTransactionID);
317 } else if ($this->beforeTransactionID) {
318 $pager->setAfterID($this->beforeTransactionID);
319 }
320
321 $transactions = id(new ConpherenceTransactionQuery())
322 ->setViewer($this->getViewer())
323 ->withObjectPHIDs(array_keys($conpherences))
324 ->needHandles(true)
325 ->executeWithCursorPager($pager);
326
327 $transactions = mgroup($transactions, 'getObjectPHID');
328 foreach ($conpherences as $phid => $conpherence) {
329 $current_transactions = idx($transactions, $phid, array());
330 $handles = array();
331 foreach ($current_transactions as $transaction) {
332 $handles += $transaction->getHandles();
333 }
334 $conpherence->attachHandles($conpherence->getHandles() + $handles);
335 $conpherence->attachTransactions($current_transactions);
336 }
337 return $this;
338 }
339
340 public function getQueryApplicationClass() {
341 return PhabricatorConpherenceApplication::class;
342 }
343
344 protected function getPrimaryTableAlias() {
345 return 'thread';
346 }
347
348}