@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
3final class PhabricatorSubscriptionsSubscribersPolicyRule
4 extends PhabricatorPolicyRule {
5
6 private $subscribed = array();
7 private $sourcePHIDs = array();
8
9 public function getObjectPolicyKey() {
10 return 'subscriptions.subscribers';
11 }
12
13 public function getObjectPolicyName() {
14 return pht('Subscribers');
15 }
16
17 public function getPolicyExplanation() {
18 return pht('Subscribers can take this action.');
19 }
20
21 public function getRuleDescription() {
22 return pht('subscribers');
23 }
24
25 public function canApplyToObject(PhabricatorPolicyInterface $object) {
26 return ($object instanceof PhabricatorSubscribableInterface);
27 }
28
29 public function willApplyRules(
30 PhabricatorUser $viewer,
31 array $values,
32 array $objects) {
33
34 // We want to let the user see the object if they're a subscriber or
35 // a member of any project which is a subscriber. Additionally, because
36 // subscriber state is complex, we need to read hints passed from
37 // the TransactionEditor to predict policy state after transactions apply.
38
39 $viewer_phid = $viewer->getPHID();
40 if (!$viewer_phid) {
41 return;
42 }
43
44 if (empty($this->subscribed[$viewer_phid])) {
45 $this->subscribed[$viewer_phid] = array();
46 }
47
48 // Load the project PHIDs the user is a member of. We use the omnipotent
49 // user here because projects may themselves have "Subscribers" visibility
50 // policies and we don't want to get stuck in an infinite stack of
51 // recursive policy checks. See T13106.
52 if (!isset($this->sourcePHIDs[$viewer_phid])) {
53 $projects = id(new PhabricatorProjectQuery())
54 ->setViewer(PhabricatorUser::getOmnipotentUser())
55 ->withMemberPHIDs(array($viewer_phid))
56 ->execute();
57
58 $source_phids = mpull($projects, 'getPHID');
59 $source_phids[] = $viewer_phid;
60
61 $this->sourcePHIDs[$viewer_phid] = $source_phids;
62 }
63
64 // Look for transaction hints.
65 foreach ($objects as $key => $object) {
66 $cache = $this->getTransactionHint($object);
67 if ($cache === null) {
68 // We don't have a hint for this object, so we'll deal with it below.
69 continue;
70 }
71
72 // We have a hint, so use that as the source of truth.
73 unset($objects[$key]);
74
75 foreach ($this->sourcePHIDs[$viewer_phid] as $source_phid) {
76 if (isset($cache[$source_phid])) {
77 $this->subscribed[$viewer_phid][$object->getPHID()] = true;
78 break;
79 }
80 }
81 }
82
83 $phids = mpull($objects, 'getPHID');
84 if (!$phids) {
85 return;
86 }
87
88 $edge_query = id(new PhabricatorEdgeQuery())
89 ->withSourcePHIDs($this->sourcePHIDs[$viewer_phid])
90 ->withEdgeTypes(
91 array(
92 PhabricatorSubscribedToObjectEdgeType::EDGECONST,
93 ))
94 ->withDestinationPHIDs($phids);
95
96 $edge_query->execute();
97
98 $subscribed = $edge_query->getDestinationPHIDs();
99 if (!$subscribed) {
100 return;
101 }
102
103 $this->subscribed[$viewer_phid] += array_fill_keys($subscribed, true);
104 }
105
106 public function applyRule(
107 PhabricatorUser $viewer,
108 $value,
109 PhabricatorPolicyInterface $object) {
110
111 $viewer_phid = $viewer->getPHID();
112 if (!$viewer_phid) {
113 return false;
114 }
115
116 if ($object->isAutomaticallySubscribed($viewer_phid)) {
117 return true;
118 }
119
120 $subscribed = idx($this->subscribed, $viewer_phid);
121 return isset($subscribed[$object->getPHID()]);
122 }
123
124 public function getValueControlType() {
125 return self::CONTROL_TYPE_NONE;
126 }
127
128}