Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/owners/query/PhabricatorOwnersPackageQuery.php
12256 views
1
<?php
2
3
final class PhabricatorOwnersPackageQuery
4
extends PhabricatorCursorPagedPolicyAwareQuery {
5
6
private $ids;
7
private $phids;
8
private $ownerPHIDs;
9
private $authorityPHIDs;
10
private $repositoryPHIDs;
11
private $paths;
12
private $statuses;
13
private $authorityModes;
14
15
private $controlMap = array();
16
private $controlResults;
17
18
private $needPaths;
19
20
21
/**
22
* Query owner PHIDs exactly. This does not expand authorities, so a user
23
* PHID will not match projects the user is a member of.
24
*/
25
public function withOwnerPHIDs(array $phids) {
26
$this->ownerPHIDs = $phids;
27
return $this;
28
}
29
30
/**
31
* Query owner authority. This will expand authorities, so a user PHID will
32
* match both packages they own directly and packages owned by a project they
33
* are a member of.
34
*/
35
public function withAuthorityPHIDs(array $phids) {
36
$this->authorityPHIDs = $phids;
37
return $this;
38
}
39
40
public function withPHIDs(array $phids) {
41
$this->phids = $phids;
42
return $this;
43
}
44
45
public function withIDs(array $ids) {
46
$this->ids = $ids;
47
return $this;
48
}
49
50
public function withRepositoryPHIDs(array $phids) {
51
$this->repositoryPHIDs = $phids;
52
return $this;
53
}
54
55
public function withPaths(array $paths) {
56
$this->paths = $paths;
57
return $this;
58
}
59
60
public function withStatuses(array $statuses) {
61
$this->statuses = $statuses;
62
return $this;
63
}
64
65
public function withControl($repository_phid, array $paths) {
66
if (empty($this->controlMap[$repository_phid])) {
67
$this->controlMap[$repository_phid] = array();
68
}
69
70
foreach ($paths as $path) {
71
$path = (string)$path;
72
$this->controlMap[$repository_phid][$path] = $path;
73
}
74
75
// We need to load paths to execute control queries.
76
$this->needPaths = true;
77
78
return $this;
79
}
80
81
public function withAuthorityModes(array $modes) {
82
$this->authorityModes = $modes;
83
return $this;
84
}
85
86
public function withNameNgrams($ngrams) {
87
return $this->withNgramsConstraint(
88
new PhabricatorOwnersPackageNameNgrams(),
89
$ngrams);
90
}
91
92
public function needPaths($need_paths) {
93
$this->needPaths = $need_paths;
94
return $this;
95
}
96
97
public function newResultObject() {
98
return new PhabricatorOwnersPackage();
99
}
100
101
protected function willExecute() {
102
$this->controlResults = array();
103
}
104
105
protected function willFilterPage(array $packages) {
106
$package_ids = mpull($packages, 'getID');
107
108
$owners = id(new PhabricatorOwnersOwner())->loadAllWhere(
109
'packageID IN (%Ld)',
110
$package_ids);
111
$owners = mgroup($owners, 'getPackageID');
112
foreach ($packages as $package) {
113
$package->attachOwners(idx($owners, $package->getID(), array()));
114
}
115
116
return $packages;
117
}
118
119
protected function didFilterPage(array $packages) {
120
$package_ids = mpull($packages, 'getID');
121
122
if ($this->needPaths) {
123
$paths = id(new PhabricatorOwnersPath())->loadAllWhere(
124
'packageID IN (%Ld)',
125
$package_ids);
126
$paths = mgroup($paths, 'getPackageID');
127
128
foreach ($packages as $package) {
129
$package->attachPaths(idx($paths, $package->getID(), array()));
130
}
131
}
132
133
if ($this->controlMap) {
134
foreach ($packages as $package) {
135
// If this package is archived, it's no longer a controlling package
136
// for any path. In particular, it can not force active packages with
137
// weak dominion to give up control.
138
if ($package->isArchived()) {
139
continue;
140
}
141
142
$this->controlResults[$package->getID()] = $package;
143
}
144
}
145
146
return $packages;
147
}
148
149
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
150
$joins = parent::buildJoinClauseParts($conn);
151
152
if ($this->shouldJoinOwnersTable()) {
153
$joins[] = qsprintf(
154
$conn,
155
'JOIN %T o ON o.packageID = p.id',
156
id(new PhabricatorOwnersOwner())->getTableName());
157
}
158
159
if ($this->shouldJoinPathTable()) {
160
$joins[] = qsprintf(
161
$conn,
162
'JOIN %T rpath ON rpath.packageID = p.id',
163
id(new PhabricatorOwnersPath())->getTableName());
164
}
165
166
return $joins;
167
}
168
169
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
170
$where = parent::buildWhereClauseParts($conn);
171
172
if ($this->phids !== null) {
173
$where[] = qsprintf(
174
$conn,
175
'p.phid IN (%Ls)',
176
$this->phids);
177
}
178
179
if ($this->ids !== null) {
180
$where[] = qsprintf(
181
$conn,
182
'p.id IN (%Ld)',
183
$this->ids);
184
}
185
186
if ($this->repositoryPHIDs !== null) {
187
$where[] = qsprintf(
188
$conn,
189
'rpath.repositoryPHID IN (%Ls)',
190
$this->repositoryPHIDs);
191
}
192
193
if ($this->authorityPHIDs !== null) {
194
$authority_phids = $this->expandAuthority($this->authorityPHIDs);
195
$where[] = qsprintf(
196
$conn,
197
'o.userPHID IN (%Ls)',
198
$authority_phids);
199
}
200
201
if ($this->ownerPHIDs !== null) {
202
$where[] = qsprintf(
203
$conn,
204
'o.userPHID IN (%Ls)',
205
$this->ownerPHIDs);
206
}
207
208
if ($this->paths !== null) {
209
$where[] = qsprintf(
210
$conn,
211
'rpath.pathIndex IN (%Ls)',
212
$this->getFragmentIndexesForPaths($this->paths));
213
}
214
215
if ($this->statuses !== null) {
216
$where[] = qsprintf(
217
$conn,
218
'p.status IN (%Ls)',
219
$this->statuses);
220
}
221
222
if ($this->controlMap) {
223
$clauses = array();
224
foreach ($this->controlMap as $repository_phid => $paths) {
225
$indexes = $this->getFragmentIndexesForPaths($paths);
226
227
$clauses[] = qsprintf(
228
$conn,
229
'(rpath.repositoryPHID = %s AND rpath.pathIndex IN (%Ls))',
230
$repository_phid,
231
$indexes);
232
}
233
$where[] = qsprintf($conn, '%LO', $clauses);
234
}
235
236
if ($this->authorityModes !== null) {
237
$where[] = qsprintf(
238
$conn,
239
'authorityMode IN (%Ls)',
240
$this->authorityModes);
241
}
242
243
return $where;
244
}
245
246
protected function shouldGroupQueryResultRows() {
247
if ($this->shouldJoinOwnersTable()) {
248
return true;
249
}
250
251
if ($this->shouldJoinPathTable()) {
252
return true;
253
}
254
255
return parent::shouldGroupQueryResultRows();
256
}
257
258
public function getBuiltinOrders() {
259
return array(
260
'name' => array(
261
'vector' => array('name'),
262
'name' => pht('Name'),
263
),
264
) + parent::getBuiltinOrders();
265
}
266
267
public function getOrderableColumns() {
268
return parent::getOrderableColumns() + array(
269
'name' => array(
270
'table' => $this->getPrimaryTableAlias(),
271
'column' => 'name',
272
'type' => 'string',
273
'unique' => true,
274
'reverse' => true,
275
),
276
);
277
}
278
279
protected function newPagingMapFromPartialObject($object) {
280
return array(
281
'id' => (int)$object->getID(),
282
'name' => $object->getName(),
283
);
284
}
285
286
public function getQueryApplicationClass() {
287
return 'PhabricatorOwnersApplication';
288
}
289
290
protected function getPrimaryTableAlias() {
291
return 'p';
292
}
293
294
private function shouldJoinOwnersTable() {
295
if ($this->ownerPHIDs !== null) {
296
return true;
297
}
298
299
if ($this->authorityPHIDs !== null) {
300
return true;
301
}
302
303
return false;
304
}
305
306
private function shouldJoinPathTable() {
307
if ($this->repositoryPHIDs !== null) {
308
return true;
309
}
310
311
if ($this->paths !== null) {
312
return true;
313
}
314
315
if ($this->controlMap) {
316
return true;
317
}
318
319
return false;
320
}
321
322
private function expandAuthority(array $phids) {
323
$projects = id(new PhabricatorProjectQuery())
324
->setViewer($this->getViewer())
325
->withMemberPHIDs($phids)
326
->execute();
327
$project_phids = mpull($projects, 'getPHID');
328
329
return array_fuse($phids) + array_fuse($project_phids);
330
}
331
332
private function getFragmentsForPaths(array $paths) {
333
$fragments = array();
334
335
foreach ($paths as $path) {
336
foreach (PhabricatorOwnersPackage::splitPath($path) as $fragment) {
337
$fragments[$fragment] = $fragment;
338
}
339
}
340
341
return $fragments;
342
}
343
344
private function getFragmentIndexesForPaths(array $paths) {
345
$indexes = array();
346
347
foreach ($this->getFragmentsForPaths($paths) as $fragment) {
348
$indexes[] = PhabricatorHash::digestForIndex($fragment);
349
}
350
351
return $indexes;
352
}
353
354
355
/* -( Path Control )------------------------------------------------------- */
356
357
358
/**
359
* Get a list of all packages which control a path or its parent directories,
360
* ordered from weakest to strongest.
361
*
362
* The first package has the most specific claim on the path; the last
363
* package has the most general claim. Multiple packages may have claims of
364
* equal strength, so this ordering is primarily one of usability and
365
* convenience.
366
*
367
* @return list<PhabricatorOwnersPackage> List of controlling packages.
368
*/
369
public function getControllingPackagesForPath(
370
$repository_phid,
371
$path,
372
$ignore_dominion = false) {
373
$path = (string)$path;
374
375
if (!isset($this->controlMap[$repository_phid][$path])) {
376
throw new PhutilInvalidStateException('withControl');
377
}
378
379
if ($this->controlResults === null) {
380
throw new PhutilInvalidStateException('execute');
381
}
382
383
$packages = $this->controlResults;
384
$weak_dominion = PhabricatorOwnersPackage::DOMINION_WEAK;
385
386
$path_fragments = PhabricatorOwnersPackage::splitPath($path);
387
$fragment_count = count($path_fragments);
388
389
$matches = array();
390
foreach ($packages as $package_id => $package) {
391
$best_match = null;
392
$include = false;
393
394
$repository_paths = $package->getPathsForRepository($repository_phid);
395
foreach ($repository_paths as $package_path) {
396
$strength = $package_path->getPathMatchStrength(
397
$path_fragments,
398
$fragment_count);
399
if ($strength > $best_match) {
400
$best_match = $strength;
401
$include = !$package_path->getExcluded();
402
}
403
}
404
405
if ($best_match && $include) {
406
if ($ignore_dominion) {
407
$is_weak = false;
408
} else {
409
$is_weak = ($package->getDominion() == $weak_dominion);
410
}
411
$matches[$package_id] = array(
412
'strength' => $best_match,
413
'weak' => $is_weak,
414
'package' => $package,
415
);
416
}
417
}
418
419
// At each strength level, drop weak packages if there are also strong
420
// packages of the same strength.
421
$strength_map = igroup($matches, 'strength');
422
foreach ($strength_map as $strength => $package_list) {
423
$any_strong = false;
424
foreach ($package_list as $package_id => $package) {
425
if (!$package['weak']) {
426
$any_strong = true;
427
break;
428
}
429
}
430
if ($any_strong) {
431
foreach ($package_list as $package_id => $package) {
432
if ($package['weak']) {
433
unset($matches[$package_id]);
434
}
435
}
436
}
437
}
438
439
$matches = isort($matches, 'strength');
440
$matches = array_reverse($matches);
441
442
$strongest = null;
443
foreach ($matches as $package_id => $match) {
444
if ($strongest === null) {
445
$strongest = $match['strength'];
446
}
447
448
if ($match['strength'] === $strongest) {
449
continue;
450
}
451
452
if ($match['weak']) {
453
unset($matches[$package_id]);
454
}
455
}
456
457
return array_values(ipull($matches, 'package'));
458
}
459
460
}
461
462