Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/maniphest/editor/ManiphestEditEngine.php
12256 views
1
<?php
2
3
final class ManiphestEditEngine
4
extends PhabricatorEditEngine {
5
6
const ENGINECONST = 'maniphest.task';
7
8
public function getEngineName() {
9
return pht('Maniphest Tasks');
10
}
11
12
public function getSummaryHeader() {
13
return pht('Configure Maniphest Task Forms');
14
}
15
16
public function getSummaryText() {
17
return pht('Configure how users create and edit tasks.');
18
}
19
20
public function getEngineApplicationClass() {
21
return 'PhabricatorManiphestApplication';
22
}
23
24
public function isDefaultQuickCreateEngine() {
25
return true;
26
}
27
28
public function getQuickCreateOrderVector() {
29
return id(new PhutilSortVector())->addInt(100);
30
}
31
32
protected function newEditableObject() {
33
return ManiphestTask::initializeNewTask($this->getViewer());
34
}
35
36
protected function newObjectQuery() {
37
return id(new ManiphestTaskQuery());
38
}
39
40
protected function getObjectCreateTitleText($object) {
41
return pht('Create New Task');
42
}
43
44
protected function getObjectEditTitleText($object) {
45
return pht('Edit Task: %s', $object->getTitle());
46
}
47
48
protected function getObjectEditShortText($object) {
49
return $object->getMonogram();
50
}
51
52
protected function getObjectCreateShortText() {
53
return pht('Create Task');
54
}
55
56
protected function getObjectName() {
57
return pht('Task');
58
}
59
60
protected function getEditorURI() {
61
return $this->getApplication()->getApplicationURI('task/edit/');
62
}
63
64
protected function getCommentViewHeaderText($object) {
65
return pht('Weigh In');
66
}
67
68
protected function getCommentViewButtonText($object) {
69
return pht('Set Sail for Adventure');
70
}
71
72
protected function getObjectViewURI($object) {
73
return '/'.$object->getMonogram();
74
}
75
76
protected function buildCustomEditFields($object) {
77
$status_map = $this->getTaskStatusMap($object);
78
$priority_map = $this->getTaskPriorityMap($object);
79
80
$alias_map = ManiphestTaskPriority::getTaskPriorityAliasMap();
81
82
if ($object->isClosed()) {
83
$default_status = ManiphestTaskStatus::getDefaultStatus();
84
} else {
85
$default_status = ManiphestTaskStatus::getDefaultClosedStatus();
86
}
87
88
if ($object->getOwnerPHID()) {
89
$owner_value = array($object->getOwnerPHID());
90
} else {
91
$owner_value = array($this->getViewer()->getPHID());
92
}
93
94
$column_documentation = pht(<<<EODOCS
95
You can use this transaction type to create a task into a particular workboard
96
column, or move an existing task between columns.
97
98
The transaction value can be specified in several forms. Some are simpler but
99
less powerful, while others are more complex and more powerful.
100
101
The simplest valid value is a single column PHID:
102
103
```lang=json
104
"PHID-PCOL-1111"
105
```
106
107
This will move the task into that column, or create the task into that column
108
if you are creating a new task. If the task is currently on the board, it will
109
be moved out of any exclusive columns. If the task is not currently on the
110
board, it will be added to the board.
111
112
You can also perform multiple moves at the same time by passing a list of
113
PHIDs:
114
115
```lang=json
116
["PHID-PCOL-2222", "PHID-PCOL-3333"]
117
```
118
119
This is equivalent to performing each move individually.
120
121
The most complex and most powerful form uses a dictionary to provide additional
122
information about the move, including an optional specific position within the
123
column.
124
125
The target column should be identified as `columnPHID`, and you may select a
126
position by passing either `beforePHIDs` or `afterPHIDs`, specifying the PHIDs
127
of tasks currently in the column that you want to move this task before or
128
after:
129
130
```lang=json
131
[
132
{
133
"columnPHID": "PHID-PCOL-4444",
134
"beforePHIDs": ["PHID-TASK-5555"]
135
}
136
]
137
```
138
139
When you specify multiple PHIDs, the task will be moved adjacent to the first
140
valid PHID found in either of the lists. This allows positional moves to
141
generally work as users expect even if the client view of the board has fallen
142
out of date and some of the nearby tasks have moved elsewhere.
143
EODOCS
144
);
145
146
$column_map = $this->getColumnMap($object);
147
148
$fields = array(
149
id(new PhabricatorHandlesEditField())
150
->setKey('parent')
151
->setLabel(pht('Parent Task'))
152
->setDescription(pht('Task to make this a subtask of.'))
153
->setConduitDescription(pht('Create as a subtask of another task.'))
154
->setConduitTypeDescription(pht('PHID of the parent task.'))
155
->setAliases(array('parentPHID'))
156
->setTransactionType(ManiphestTaskParentTransaction::TRANSACTIONTYPE)
157
->setHandleParameterType(new ManiphestTaskListHTTPParameterType())
158
->setSingleValue(null)
159
->setIsReorderable(false)
160
->setIsDefaultable(false)
161
->setIsLockable(false),
162
id(new PhabricatorColumnsEditField())
163
->setKey('column')
164
->setLabel(pht('Column'))
165
->setDescription(pht('Create a task in a workboard column.'))
166
->setConduitDescription(
167
pht('Move a task to one or more workboard columns.'))
168
->setConduitTypeDescription(
169
pht('List of columns to move the task to.'))
170
->setConduitDocumentation($column_documentation)
171
->setAliases(array('columnPHID', 'columns', 'columnPHIDs'))
172
->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS)
173
->setIsReorderable(false)
174
->setIsDefaultable(false)
175
->setIsLockable(false)
176
->setCommentActionLabel(pht('Move on Workboard'))
177
->setCommentActionOrder(2000)
178
->setColumnMap($column_map),
179
id(new PhabricatorTextEditField())
180
->setKey('title')
181
->setLabel(pht('Title'))
182
->setBulkEditLabel(pht('Set title to'))
183
->setDescription(pht('Name of the task.'))
184
->setConduitDescription(pht('Rename the task.'))
185
->setConduitTypeDescription(pht('New task name.'))
186
->setTransactionType(ManiphestTaskTitleTransaction::TRANSACTIONTYPE)
187
->setIsRequired(true)
188
->setValue($object->getTitle()),
189
id(new PhabricatorUsersEditField())
190
->setKey('owner')
191
->setAliases(array('ownerPHID', 'assign', 'assigned'))
192
->setLabel(pht('Assigned To'))
193
->setBulkEditLabel(pht('Assign to'))
194
->setDescription(pht('User who is responsible for the task.'))
195
->setConduitDescription(pht('Reassign the task.'))
196
->setConduitTypeDescription(
197
pht('New task owner, or `null` to unassign.'))
198
->setTransactionType(ManiphestTaskOwnerTransaction::TRANSACTIONTYPE)
199
->setIsCopyable(true)
200
->setIsNullable(true)
201
->setSingleValue($object->getOwnerPHID())
202
->setCommentActionLabel(pht('Assign / Claim'))
203
->setCommentActionValue($owner_value),
204
id(new PhabricatorSelectEditField())
205
->setKey('status')
206
->setLabel(pht('Status'))
207
->setBulkEditLabel(pht('Set status to'))
208
->setDescription(pht('Status of the task.'))
209
->setConduitDescription(pht('Change the task status.'))
210
->setConduitTypeDescription(pht('New task status constant.'))
211
->setTransactionType(ManiphestTaskStatusTransaction::TRANSACTIONTYPE)
212
->setIsCopyable(true)
213
->setValue($object->getStatus())
214
->setOptions($status_map)
215
->setCommentActionLabel(pht('Change Status'))
216
->setCommentActionValue($default_status),
217
id(new PhabricatorSelectEditField())
218
->setKey('priority')
219
->setLabel(pht('Priority'))
220
->setBulkEditLabel(pht('Set priority to'))
221
->setDescription(pht('Priority of the task.'))
222
->setConduitDescription(pht('Change the priority of the task.'))
223
->setConduitTypeDescription(pht('New task priority constant.'))
224
->setTransactionType(ManiphestTaskPriorityTransaction::TRANSACTIONTYPE)
225
->setIsCopyable(true)
226
->setValue($object->getPriorityKeyword())
227
->setOptions($priority_map)
228
->setOptionAliases($alias_map)
229
->setCommentActionLabel(pht('Change Priority')),
230
);
231
232
if (ManiphestTaskPoints::getIsEnabled()) {
233
$points_label = ManiphestTaskPoints::getPointsLabel();
234
$action_label = ManiphestTaskPoints::getPointsActionLabel();
235
236
$fields[] = id(new PhabricatorPointsEditField())
237
->setKey('points')
238
->setLabel($points_label)
239
->setBulkEditLabel($action_label)
240
->setDescription(pht('Point value of the task.'))
241
->setConduitDescription(pht('Change the task point value.'))
242
->setConduitTypeDescription(pht('New task point value.'))
243
->setTransactionType(ManiphestTaskPointsTransaction::TRANSACTIONTYPE)
244
->setIsCopyable(true)
245
->setValue($object->getPoints())
246
->setCommentActionLabel($action_label);
247
}
248
249
$fields[] = id(new PhabricatorRemarkupEditField())
250
->setKey('description')
251
->setLabel(pht('Description'))
252
->setBulkEditLabel(pht('Set description to'))
253
->setDescription(pht('Task description.'))
254
->setConduitDescription(pht('Update the task description.'))
255
->setConduitTypeDescription(pht('New task description.'))
256
->setTransactionType(ManiphestTaskDescriptionTransaction::TRANSACTIONTYPE)
257
->setValue($object->getDescription())
258
->setPreviewPanel(
259
id(new PHUIRemarkupPreviewPanel())
260
->setHeader(pht('Description Preview')));
261
262
$parent_type = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
263
$subtask_type = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
264
$commit_type = ManiphestTaskHasCommitEdgeType::EDGECONST;
265
266
$src_phid = $object->getPHID();
267
if ($src_phid) {
268
$edge_query = id(new PhabricatorEdgeQuery())
269
->withSourcePHIDs(array($src_phid))
270
->withEdgeTypes(
271
array(
272
$parent_type,
273
$subtask_type,
274
$commit_type,
275
));
276
$edge_query->execute();
277
278
$parent_phids = $edge_query->getDestinationPHIDs(
279
array($src_phid),
280
array($parent_type));
281
282
$subtask_phids = $edge_query->getDestinationPHIDs(
283
array($src_phid),
284
array($subtask_type));
285
286
$commit_phids = $edge_query->getDestinationPHIDs(
287
array($src_phid),
288
array($commit_type));
289
} else {
290
$parent_phids = array();
291
$subtask_phids = array();
292
$commit_phids = array();
293
}
294
295
$fields[] = id(new PhabricatorHandlesEditField())
296
->setKey('parents')
297
->setLabel(pht('Parents'))
298
->setDescription(pht('Parent tasks.'))
299
->setConduitDescription(pht('Change the parents of this task.'))
300
->setConduitTypeDescription(pht('List of parent task PHIDs.'))
301
->setUseEdgeTransactions(true)
302
->setIsFormField(false)
303
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
304
->setMetadataValue('edge:type', $parent_type)
305
->setValue($parent_phids);
306
307
$fields[] = id(new PhabricatorHandlesEditField())
308
->setKey('subtasks')
309
->setLabel(pht('Subtasks'))
310
->setDescription(pht('Subtasks.'))
311
->setConduitDescription(pht('Change the subtasks of this task.'))
312
->setConduitTypeDescription(pht('List of subtask PHIDs.'))
313
->setUseEdgeTransactions(true)
314
->setIsFormField(false)
315
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
316
->setMetadataValue('edge:type', $subtask_type)
317
->setValue($subtask_phids);
318
319
$fields[] = id(new PhabricatorHandlesEditField())
320
->setKey('commits')
321
->setLabel(pht('Commits'))
322
->setDescription(pht('Related commits.'))
323
->setConduitDescription(pht('Change the related commits for this task.'))
324
->setConduitTypeDescription(pht('List of related commit PHIDs.'))
325
->setUseEdgeTransactions(true)
326
->setIsFormField(false)
327
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
328
->setMetadataValue('edge:type', $commit_type)
329
->setValue($commit_phids);
330
331
return $fields;
332
}
333
334
private function getTaskStatusMap(ManiphestTask $task) {
335
$status_map = ManiphestTaskStatus::getTaskStatusMap();
336
337
$current_status = $task->getStatus();
338
339
// If the current status is something we don't recognize (maybe an older
340
// status which was deleted), put a dummy entry in the status map so that
341
// saving the form doesn't destroy any data by accident.
342
if (idx($status_map, $current_status) === null) {
343
$status_map[$current_status] = pht('<Unknown: %s>', $current_status);
344
}
345
346
$dup_status = ManiphestTaskStatus::getDuplicateStatus();
347
foreach ($status_map as $status => $status_name) {
348
// Always keep the task's current status.
349
if ($status == $current_status) {
350
continue;
351
}
352
353
// Don't allow tasks to be changed directly into "Closed, Duplicate"
354
// status. Instead, you have to merge them. See T4819.
355
if ($status == $dup_status) {
356
unset($status_map[$status]);
357
continue;
358
}
359
360
// Don't let new or existing tasks be moved into a disabled status.
361
if (ManiphestTaskStatus::isDisabledStatus($status)) {
362
unset($status_map[$status]);
363
continue;
364
}
365
}
366
367
return $status_map;
368
}
369
370
private function getTaskPriorityMap(ManiphestTask $task) {
371
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
372
$priority_keywords = ManiphestTaskPriority::getTaskPriorityKeywordsMap();
373
$current_priority = $task->getPriority();
374
$results = array();
375
376
foreach ($priority_map as $priority => $priority_name) {
377
$disabled = ManiphestTaskPriority::isDisabledPriority($priority);
378
if ($disabled && !($priority == $current_priority)) {
379
continue;
380
}
381
382
$keyword = head(idx($priority_keywords, $priority));
383
$results[$keyword] = $priority_name;
384
}
385
386
// If the current value isn't a legitimate one, put it in the dropdown
387
// anyway so saving the form doesn't cause any side effects.
388
if (idx($priority_map, $current_priority) === null) {
389
$results[ManiphestTaskPriority::UNKNOWN_PRIORITY_KEYWORD] = pht(
390
'<Unknown: %s>',
391
$current_priority);
392
}
393
394
return $results;
395
}
396
397
protected function newEditResponse(
398
AphrontRequest $request,
399
$object,
400
array $xactions) {
401
402
$response_type = $request->getStr('responseType');
403
$is_card = ($response_type === 'card');
404
405
if ($is_card) {
406
// Reload the task to make sure we pick up the final task state.
407
$viewer = $this->getViewer();
408
$task = id(new ManiphestTaskQuery())
409
->setViewer($viewer)
410
->withIDs(array($object->getID()))
411
->needSubscriberPHIDs(true)
412
->needProjectPHIDs(true)
413
->executeOne();
414
415
return $this->buildCardResponse($task);
416
}
417
418
return parent::newEditResponse($request, $object, $xactions);
419
}
420
421
private function buildCardResponse(ManiphestTask $task) {
422
$controller = $this->getController();
423
$request = $controller->getRequest();
424
$viewer = $request->getViewer();
425
426
$column_phid = $request->getStr('columnPHID');
427
428
$visible_phids = $request->getStrList('visiblePHIDs');
429
if (!$visible_phids) {
430
$visible_phids = array();
431
}
432
433
$column = id(new PhabricatorProjectColumnQuery())
434
->setViewer($viewer)
435
->withPHIDs(array($column_phid))
436
->executeOne();
437
if (!$column) {
438
return new Aphront404Response();
439
}
440
441
$board_phid = $column->getProjectPHID();
442
$object_phid = $task->getPHID();
443
444
$order = $request->getStr('order');
445
if ($order) {
446
$ordering = PhabricatorProjectColumnOrder::getOrderByKey($order);
447
$ordering = id(clone $ordering)
448
->setViewer($viewer);
449
} else {
450
$ordering = null;
451
}
452
453
$engine = id(new PhabricatorBoardResponseEngine())
454
->setViewer($viewer)
455
->setBoardPHID($board_phid)
456
->setUpdatePHIDs(array($object_phid))
457
->setVisiblePHIDs($visible_phids);
458
459
if ($ordering) {
460
$engine->setOrdering($ordering);
461
}
462
463
return $engine->buildResponse();
464
}
465
466
private function getColumnMap(ManiphestTask $task) {
467
$phid = $task->getPHID();
468
if (!$phid) {
469
return array();
470
}
471
472
$board_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
473
$phid,
474
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
475
if (!$board_phids) {
476
return array();
477
}
478
479
$viewer = $this->getViewer();
480
481
$layout_engine = id(new PhabricatorBoardLayoutEngine())
482
->setViewer($viewer)
483
->setBoardPHIDs($board_phids)
484
->setObjectPHIDs(array($task->getPHID()))
485
->executeLayout();
486
487
$map = array();
488
foreach ($board_phids as $board_phid) {
489
$in_columns = $layout_engine->getObjectColumns($board_phid, $phid);
490
$in_columns = mpull($in_columns, null, 'getPHID');
491
492
$all_columns = $layout_engine->getColumns($board_phid);
493
if (!$all_columns) {
494
// This could be a project with no workboard, or a project the viewer
495
// does not have permission to see.
496
continue;
497
}
498
499
$board = head($all_columns)->getProject();
500
501
$options = array();
502
foreach ($all_columns as $column) {
503
$name = $column->getDisplayName();
504
505
$is_hidden = $column->isHidden();
506
$is_selected = isset($in_columns[$column->getPHID()]);
507
508
// Don't show hidden, subproject or milestone columns in this map
509
// unless the object is currently in the column.
510
$skip_column = ($is_hidden || $column->getProxyPHID());
511
if ($skip_column) {
512
if (!$is_selected) {
513
continue;
514
}
515
}
516
517
if ($is_hidden) {
518
$name = pht('(%s)', $name);
519
}
520
521
if ($is_selected) {
522
$name = pht("\xE2\x97\x8F %s", $name);
523
} else {
524
$name = pht("\xE2\x97\x8B %s", $name);
525
}
526
527
$option = array(
528
'key' => $column->getPHID(),
529
'label' => $name,
530
'selected' => (bool)$is_selected,
531
);
532
533
$options[] = $option;
534
}
535
536
$map[] = array(
537
'label' => $board->getDisplayName(),
538
'options' => $options,
539
);
540
}
541
542
$map = isort($map, 'label');
543
$map = array_values($map);
544
545
return $map;
546
}
547
548
549
}
550
551