@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 HeraldNewController extends HeraldController {
4
5 public function handleRequest(AphrontRequest $request) {
6 $viewer = $this->getViewer();
7
8 $adapter_type_map = HeraldAdapter::getEnabledAdapterMap($viewer);
9 $adapter_type = $request->getStr('adapter');
10
11 if (!$adapter_type || !isset($adapter_type_map[$adapter_type])) {
12 $title = pht('Create Herald Rule');
13 $content = $this->newAdapterMenu($title);
14 } else {
15 $adapter = HeraldAdapter::getAdapterForContentType($adapter_type);
16
17 $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap();
18 $rule_type = $request->getStr('type');
19
20 if (!$rule_type || !isset($rule_type_map[$rule_type])) {
21 $title = pht(
22 'Create Herald Rule: %s',
23 $adapter->getAdapterContentName());
24
25 $content = $this->newTypeMenu($adapter, $title);
26 } else {
27 if ($rule_type !== HeraldRuleTypeConfig::RULE_TYPE_OBJECT) {
28 $target_phid = null;
29 $target_okay = true;
30 } else {
31 $object_name = $request->getStr('objectName');
32 $target_okay = false;
33
34 $errors = array();
35 $e_object = null;
36
37 if ($request->isFormPost()) {
38 if (strlen($object_name)) {
39 $target_object = id(new PhabricatorObjectQuery())
40 ->setViewer($viewer)
41 ->withNames(array($object_name))
42 ->executeOne();
43 if ($target_object) {
44 $can_edit = PhabricatorPolicyFilter::hasCapability(
45 $viewer,
46 $target_object,
47 PhabricatorPolicyCapability::CAN_EDIT);
48 if (!$can_edit) {
49 $errors[] = pht(
50 'You can not create a rule for that object, because you '.
51 'do not have permission to edit it. You can only create '.
52 'rules for objects you can edit.');
53 $e_object = pht('Not Editable');
54 } else {
55 if (!$adapter->canTriggerOnObject($target_object)) {
56 $errors[] = pht(
57 'This object is not of an allowed type for the rule. '.
58 'Rules can only trigger on certain objects.');
59 $e_object = pht('Invalid');
60 } else {
61 $target_phid = $target_object->getPHID();
62 }
63 }
64 } else {
65 $errors[] = pht('No object exists by that name.');
66 $e_object = pht('Invalid');
67 }
68 } else {
69 $errors[] = pht(
70 'You must choose an object to associate this rule with.');
71 $e_object = pht('Required');
72 }
73
74 $target_okay = !$errors;
75 }
76 }
77
78 if (!$target_okay) {
79 $title = pht('Choose Object');
80 $content = $this->newTargetForm(
81 $adapter,
82 $rule_type,
83 $object_name,
84 $errors,
85 $e_object,
86 $title);
87 } else {
88 $params = array(
89 'content_type' => $adapter_type,
90 'rule_type' => $rule_type,
91 'targetPHID' => $target_phid,
92 );
93
94 $edit_uri = $this->getApplicationURI('edit/');
95 $edit_uri = new PhutilURI($edit_uri, $params);
96
97 return id(new AphrontRedirectResponse())
98 ->setURI($edit_uri);
99 }
100 }
101 }
102
103 $crumbs = $this
104 ->buildApplicationCrumbs()
105 ->addTextCrumb(pht('Create Rule'))
106 ->setBorder(true);
107
108 $view = id(new PHUITwoColumnView())
109 ->setFooter($content);
110
111 return $this->newPage()
112 ->setTitle($title)
113 ->setCrumbs($crumbs)
114 ->appendChild($view);
115 }
116
117 private function newAdapterMenu($title) {
118 $viewer = $this->getViewer();
119
120 $types = HeraldAdapter::getEnabledAdapterMap($viewer);
121
122 foreach ($types as $key => $type) {
123 $types[$key] = HeraldAdapter::getAdapterForContentType($key);
124 }
125
126 $types = msort($types, 'getAdapterContentName');
127
128 $base_uri = $this->getApplicationURI('create/');
129
130 $menu = id(new PHUIObjectItemListView())
131 ->setViewer($viewer)
132 ->setBig(true);
133
134 foreach ($types as $key => $adapter) {
135 $adapter_uri = id(new PhutilURI($base_uri))
136 ->replaceQueryParam('adapter', $key);
137
138 $description = $adapter->getAdapterContentDescription();
139 $description = phutil_escape_html_newlines($description);
140
141 $item = id(new PHUIObjectItemView())
142 ->setHeader($adapter->getAdapterContentName())
143 ->setImageIcon($adapter->getAdapterContentIcon())
144 ->addAttribute($description)
145 ->setHref($adapter_uri)
146 ->setClickable(true);
147
148 $menu->addItem($item);
149 }
150
151 $box = id(new PHUIObjectBoxView())
152 ->setHeaderText($title)
153 ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
154 ->setObjectList($menu);
155
156 return id(new PHUILauncherView())
157 ->appendChild($box);
158 }
159
160 private function newTypeMenu(HeraldAdapter $adapter, $title) {
161 $viewer = $this->getViewer();
162
163 $global_capability = HeraldManageGlobalRulesCapability::CAPABILITY;
164 $can_global = $this->hasApplicationCapability($global_capability);
165
166 if ($can_global) {
167 $global_note = pht(
168 'You have permission to create and manage global rules.');
169 } else {
170 $global_note = pht(
171 'You do not have permission to create or manage global rules.');
172 }
173 $global_note = phutil_tag('em', array(), $global_note);
174
175 $specs = array(
176 HeraldRuleTypeConfig::RULE_TYPE_PERSONAL => array(
177 'name' => pht('Personal Rule'),
178 'icon' => 'fa-user',
179 'help' => pht(
180 'Personal rules notify you about events. You own them, but they can '.
181 'only affect you. Personal rules only trigger for objects you have '.
182 'permission to see.'),
183 'enabled' => true,
184 ),
185 HeraldRuleTypeConfig::RULE_TYPE_OBJECT => array(
186 'name' => pht('Object Rule'),
187 'icon' => 'fa-cube',
188 'help' => pht(
189 'Object rules notify anyone about events. They are bound to an '.
190 'object (like a repository) and can only act on that object. You '.
191 'must be able to edit an object to create object rules for it. '.
192 'Other users who can edit the object can edit its rules.'),
193 'enabled' => true,
194 ),
195 HeraldRuleTypeConfig::RULE_TYPE_GLOBAL => array(
196 'name' => pht('Global Rule'),
197 'icon' => 'fa-globe',
198 'help' => array(
199 pht(
200 'Global rules notify anyone about events. Global rules can '.
201 'bypass access control policies and act on any object.'),
202 $global_note,
203 ),
204 'enabled' => $can_global,
205 ),
206 );
207
208 $adapter_type = $adapter->getAdapterContentType();
209
210 $base_uri = new PhutilURI($this->getApplicationURI('create/'));
211
212 $adapter_uri = id(clone $base_uri)
213 ->replaceQueryParam('adapter', $adapter_type);
214
215 $menu = id(new PHUIObjectItemListView())
216 ->setUser($viewer)
217 ->setBig(true);
218
219 foreach ($specs as $rule_type => $spec) {
220 $type_uri = id(clone $adapter_uri)
221 ->replaceQueryParam('type', $rule_type);
222
223 $name = $spec['name'];
224 $icon = $spec['icon'];
225
226 $description = $spec['help'];
227 $description = (array)$description;
228
229 $enabled = $spec['enabled'];
230 if ($enabled) {
231 $enabled = $adapter->supportsRuleType($rule_type);
232 if (!$enabled) {
233 $description[] = phutil_tag(
234 'em',
235 array(),
236 pht(
237 'This rule type is not supported by the selected '.
238 'content type.'));
239 }
240 }
241
242 $description = phutil_implode_html(
243 array(
244 phutil_tag('br'),
245 phutil_tag('br'),
246 ),
247 $description);
248
249 $item = id(new PHUIObjectItemView())
250 ->setHeader($name)
251 ->setImageIcon($icon)
252 ->addAttribute($description);
253
254 if ($enabled) {
255 $item
256 ->setHref($type_uri)
257 ->setClickable(true);
258 } else {
259 $item->setDisabled(true);
260 }
261
262 $menu->addItem($item);
263 }
264
265 $box = id(new PHUIObjectBoxView())
266 ->setHeaderText($title)
267 ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
268 ->setObjectList($menu);
269
270 $box->newTailButton()
271 ->setText(pht('Back to Content Types'))
272 ->setIcon('fa-chevron-left')
273 ->setHref($base_uri);
274
275 return id(new PHUILauncherView())
276 ->appendChild($box);
277 }
278
279
280 private function newTargetForm(
281 HeraldAdapter $adapter,
282 $rule_type,
283 $object_name,
284 $errors,
285 $e_object,
286 $title) {
287
288 $viewer = $this->getViewer();
289 $content_type = $adapter->getAdapterContentType();
290 $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap();
291
292 $params = array(
293 'adapter' => $content_type,
294 'type' => $rule_type,
295 );
296
297 $form = id(new AphrontFormView())
298 ->setViewer($viewer)
299 ->appendChild(
300 id(new AphrontFormStaticControl())
301 ->setLabel(pht('Rule for'))
302 ->setValue(
303 phutil_tag(
304 'strong',
305 array(),
306 $adapter->getAdapterContentName())))
307 ->appendChild(
308 id(new AphrontFormStaticControl())
309 ->setLabel(pht('Rule Type'))
310 ->setValue(
311 phutil_tag(
312 'strong',
313 array(),
314 idx($rule_type_map, $rule_type))))
315 ->appendRemarkupInstructions(
316 pht(
317 'Choose the object this rule will act on (for example, enter '.
318 '`rX` to act on the `rX` repository, or `#project` to act on '.
319 'a project).'))
320 ->appendRemarkupInstructions(
321 $adapter->explainValidTriggerObjects())
322 ->appendChild(
323 id(new AphrontFormTextControl())
324 ->setName('objectName')
325 ->setError($e_object)
326 ->setValue($object_name)
327 ->setLabel(pht('Object')));
328
329 foreach ($params as $key => $value) {
330 $form->addHiddenInput($key, $value);
331 }
332
333 $cancel_params = $params;
334 unset($cancel_params['type']);
335
336 $cancel_uri = $this->getApplicationURI('create/');
337 $cancel_uri = new PhutilURI($cancel_uri, $cancel_params);
338
339 $form->appendChild(
340 id(new AphrontFormSubmitControl())
341 ->setValue(pht('Continue'))
342 ->addCancelButton($cancel_uri, pht('Back')));
343
344 $form_box = id(new PHUIObjectBoxView())
345 ->setHeaderText($title)
346 ->setFormErrors($errors)
347 ->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
348 ->setForm($form);
349
350 return $form_box;
351 }
352
353}