Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/view/form/control/AphrontFormPolicyControl.php
12256 views
1
<?php
2
3
final class AphrontFormPolicyControl extends AphrontFormControl {
4
5
private $object;
6
private $capability;
7
private $policies;
8
private $spacePHID;
9
private $templatePHIDType;
10
private $templateObject;
11
12
public function setPolicyObject(PhabricatorPolicyInterface $object) {
13
$this->object = $object;
14
return $this;
15
}
16
17
public function setPolicies(array $policies) {
18
assert_instances_of($policies, 'PhabricatorPolicy');
19
$this->policies = $policies;
20
return $this;
21
}
22
23
public function setSpacePHID($space_phid) {
24
$this->spacePHID = $space_phid;
25
return $this;
26
}
27
28
public function getSpacePHID() {
29
return $this->spacePHID;
30
}
31
32
public function setTemplatePHIDType($type) {
33
$this->templatePHIDType = $type;
34
return $this;
35
}
36
37
public function setTemplateObject($object) {
38
$this->templateObject = $object;
39
return $this;
40
}
41
42
public function getSerializedValue() {
43
return json_encode(array(
44
$this->getValue(),
45
$this->getSpacePHID(),
46
));
47
}
48
49
public function readSerializedValue($value) {
50
$decoded = phutil_json_decode($value);
51
$policy_value = $decoded[0];
52
$space_phid = $decoded[1];
53
$this->setValue($policy_value);
54
$this->setSpacePHID($space_phid);
55
return $this;
56
}
57
58
public function readValueFromDictionary(array $dictionary) {
59
// TODO: This is a little hacky but will only get us into trouble if we
60
// have multiple view policy controls in multiple paged form views on the
61
// same page, which seems unlikely.
62
$this->setSpacePHID(idx($dictionary, 'spacePHID'));
63
64
return parent::readValueFromDictionary($dictionary);
65
}
66
67
public function readValueFromRequest(AphrontRequest $request) {
68
// See note in readValueFromDictionary().
69
$this->setSpacePHID($request->getStr('spacePHID'));
70
71
return parent::readValueFromRequest($request);
72
}
73
74
public function setCapability($capability) {
75
$this->capability = $capability;
76
77
$labels = array(
78
PhabricatorPolicyCapability::CAN_VIEW => pht('Visible To'),
79
PhabricatorPolicyCapability::CAN_EDIT => pht('Editable By'),
80
PhabricatorPolicyCapability::CAN_JOIN => pht('Joinable By'),
81
);
82
83
if (isset($labels[$capability])) {
84
$label = $labels[$capability];
85
} else {
86
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
87
if ($capobj) {
88
$label = $capobj->getCapabilityName();
89
} else {
90
$label = pht('Capability "%s"', $capability);
91
}
92
}
93
94
$this->setLabel($label);
95
96
return $this;
97
}
98
99
protected function getCustomControlClass() {
100
return 'aphront-form-control-policy';
101
}
102
103
protected function getOptions() {
104
$capability = $this->capability;
105
$policies = $this->policies;
106
$viewer = $this->getUser();
107
108
// Check if we're missing the policy for the current control value. This
109
// is unusual, but can occur if the user is submitting a form and selected
110
// an unusual project as a policy but the change has not been saved yet.
111
$policy_map = mpull($policies, null, 'getPHID');
112
$value = $this->getValue();
113
if ($value && empty($policy_map[$value])) {
114
$handle = id(new PhabricatorHandleQuery())
115
->setViewer($viewer)
116
->withPHIDs(array($value))
117
->executeOne();
118
if ($handle->isComplete()) {
119
$policies[] = PhabricatorPolicy::newFromPolicyAndHandle(
120
$value,
121
$handle);
122
}
123
}
124
125
// Exclude object policies which don't make sense here. This primarily
126
// filters object policies associated from template capabilities (like
127
// "Default Task View Policy" being set to "Task Author") so they aren't
128
// made available on non-template capabilities (like "Can Bulk Edit").
129
foreach ($policies as $key => $policy) {
130
if ($policy->getType() != PhabricatorPolicyType::TYPE_OBJECT) {
131
continue;
132
}
133
134
$rule = PhabricatorPolicyQuery::getObjectPolicyRule($policy->getPHID());
135
if (!$rule) {
136
continue;
137
}
138
139
$target = nonempty($this->templateObject, $this->object);
140
if (!$rule->canApplyToObject($target)) {
141
unset($policies[$key]);
142
continue;
143
}
144
}
145
146
$options = array();
147
foreach ($policies as $policy) {
148
if ($policy->getPHID() == PhabricatorPolicies::POLICY_PUBLIC) {
149
// Never expose "Public" for capabilities which don't support it.
150
$capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability);
151
if (!$capobj || !$capobj->shouldAllowPublicPolicySetting()) {
152
continue;
153
}
154
}
155
156
$options[$policy->getType()][$policy->getPHID()] = array(
157
'name' => $policy->getName(),
158
'full' => $policy->getName(),
159
'icon' => $policy->getIcon(),
160
'sort' => phutil_utf8_strtolower($policy->getName()),
161
);
162
}
163
164
$type_project = PhabricatorPolicyType::TYPE_PROJECT;
165
166
// Make sure we have a "Projects" group before we adjust it.
167
if (empty($options[$type_project])) {
168
$options[$type_project] = array();
169
}
170
171
$options[$type_project] = isort($options[$type_project], 'sort');
172
173
$placeholder = id(new PhabricatorPolicy())
174
->setName(pht('Other Project...'))
175
->setIcon('fa-search');
176
177
$options[$type_project][$this->getSelectProjectKey()] = array(
178
'name' => $placeholder->getName(),
179
'full' => $placeholder->getName(),
180
'icon' => $placeholder->getIcon(),
181
);
182
183
// If we were passed several custom policy options, throw away the ones
184
// which aren't the value for this capability. For example, an object might
185
// have a custom view policy and a custom edit policy. When we render
186
// the selector for "Can View", we don't want to show the "Can Edit"
187
// custom policy -- if we did, the menu would look like this:
188
//
189
// Custom
190
// Custom Policy
191
// Custom Policy
192
//
193
// ...where one is the "view" custom policy, and one is the "edit" custom
194
// policy.
195
196
$type_custom = PhabricatorPolicyType::TYPE_CUSTOM;
197
if (!empty($options[$type_custom])) {
198
$options[$type_custom] = array_select_keys(
199
$options[$type_custom],
200
array($this->getValue()));
201
}
202
203
// If there aren't any custom policies, add a placeholder policy so we
204
// render a menu item. This allows the user to switch to a custom policy.
205
206
if (empty($options[$type_custom])) {
207
$placeholder = new PhabricatorPolicy();
208
$placeholder->setName(pht('Custom Policy...'));
209
$options[$type_custom][$this->getSelectCustomKey()] = array(
210
'name' => $placeholder->getName(),
211
'full' => $placeholder->getName(),
212
'icon' => $placeholder->getIcon(),
213
);
214
}
215
216
$options = array_select_keys(
217
$options,
218
array(
219
PhabricatorPolicyType::TYPE_GLOBAL,
220
PhabricatorPolicyType::TYPE_OBJECT,
221
PhabricatorPolicyType::TYPE_USER,
222
PhabricatorPolicyType::TYPE_CUSTOM,
223
PhabricatorPolicyType::TYPE_PROJECT,
224
));
225
226
return $options;
227
}
228
229
protected function renderInput() {
230
if (!$this->object) {
231
throw new PhutilInvalidStateException('setPolicyObject');
232
}
233
if (!$this->capability) {
234
throw new PhutilInvalidStateException('setCapability');
235
}
236
237
$policy = $this->object->getPolicy($this->capability);
238
if (!$policy) {
239
// TODO: Make this configurable.
240
$policy = PhabricatorPolicies::POLICY_USER;
241
}
242
243
if (!$this->getValue()) {
244
$this->setValue($policy);
245
}
246
247
$control_id = celerity_generate_unique_node_id();
248
$input_id = celerity_generate_unique_node_id();
249
250
$caret = phutil_tag(
251
'span',
252
array(
253
'class' => 'caret',
254
));
255
256
$input = phutil_tag(
257
'input',
258
array(
259
'type' => 'hidden',
260
'id' => $input_id,
261
'name' => $this->getName(),
262
'value' => $this->getValue(),
263
));
264
265
$options = $this->getOptions();
266
267
$order = array();
268
$labels = array();
269
foreach ($options as $key => $values) {
270
$order[$key] = array_keys($values);
271
$labels[$key] = PhabricatorPolicyType::getPolicyTypeName($key);
272
}
273
274
$flat_options = array_mergev($options);
275
276
$icons = array();
277
foreach (igroup($flat_options, 'icon') as $icon => $ignored) {
278
$icons[$icon] = id(new PHUIIconView())
279
->setIcon($icon);
280
}
281
282
if ($this->templatePHIDType) {
283
$context_path = 'template/'.$this->templatePHIDType.'/';
284
} else {
285
$object_phid = $this->object->getPHID();
286
if ($object_phid) {
287
$context_path = 'object/'.$object_phid.'/';
288
} else {
289
$object_type = phid_get_type($this->object->generatePHID());
290
$context_path = 'type/'.$object_type.'/';
291
}
292
}
293
294
Javelin::initBehavior(
295
'policy-control',
296
array(
297
'controlID' => $control_id,
298
'inputID' => $input_id,
299
'options' => $flat_options,
300
'groups' => array_keys($options),
301
'order' => $order,
302
'labels' => $labels,
303
'value' => $this->getValue(),
304
'capability' => $this->capability,
305
'editURI' => '/policy/edit/'.$context_path,
306
'customKey' => $this->getSelectCustomKey(),
307
'projectKey' => $this->getSelectProjectKey(),
308
'disabled' => $this->getDisabled(),
309
));
310
311
$selected = idx($flat_options, $this->getValue(), array());
312
$selected_icon = idx($selected, 'icon');
313
$selected_name = idx($selected, 'name');
314
315
$spaces_control = $this->buildSpacesControl();
316
317
return phutil_tag(
318
'div',
319
array(
320
),
321
array(
322
$spaces_control,
323
javelin_tag(
324
'a',
325
array(
326
'class' => 'button button-grey dropdown has-icon has-text '.
327
'policy-control',
328
'href' => '#',
329
'mustcapture' => true,
330
'sigil' => 'policy-control',
331
'id' => $control_id,
332
),
333
array(
334
$caret,
335
javelin_tag(
336
'span',
337
array(
338
'sigil' => 'policy-label',
339
'class' => 'phui-button-text',
340
),
341
array(
342
idx($icons, $selected_icon),
343
$selected_name,
344
)),
345
)),
346
$input,
347
));
348
}
349
350
public static function getSelectCustomKey() {
351
return 'select:custom';
352
}
353
354
public static function getSelectProjectKey() {
355
return 'select:project';
356
}
357
358
private function buildSpacesControl() {
359
if ($this->capability != PhabricatorPolicyCapability::CAN_VIEW) {
360
return null;
361
}
362
363
if (!($this->object instanceof PhabricatorSpacesInterface)) {
364
return null;
365
}
366
367
$viewer = $this->getUser();
368
if (!PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) {
369
return null;
370
}
371
372
$space_phid = $this->getSpacePHID();
373
if ($space_phid === null) {
374
$space_phid = $viewer->getDefaultSpacePHID();
375
}
376
377
$select = AphrontFormSelectControl::renderSelectTag(
378
$space_phid,
379
PhabricatorSpacesNamespaceQuery::getSpaceOptionsForViewer(
380
$viewer,
381
$space_phid),
382
array(
383
'disabled' => ($this->getDisabled() ? 'disabled' : null),
384
'name' => 'spacePHID',
385
'class' => 'aphront-space-select-control-knob',
386
));
387
388
return $select;
389
}
390
391
}
392
393