@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
at upstream/main 348 lines 10 kB view raw
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}