@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

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