[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 final class ManiphestTaskDetailController extends ManiphestController { 4 5 private $id; 6 7 public function shouldAllowPublic() { 8 return true; 9 } 10 11 public function willProcessRequest(array $data) { 12 $this->id = $data['id']; 13 } 14 15 public function processRequest() { 16 $request = $this->getRequest(); 17 $user = $request->getUser(); 18 19 $e_title = null; 20 21 $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); 22 23 $task = id(new ManiphestTaskQuery()) 24 ->setViewer($user) 25 ->withIDs(array($this->id)) 26 ->executeOne(); 27 if (!$task) { 28 return new Aphront404Response(); 29 } 30 31 $workflow = $request->getStr('workflow'); 32 $parent_task = null; 33 if ($workflow && is_numeric($workflow)) { 34 $parent_task = id(new ManiphestTaskQuery()) 35 ->setViewer($user) 36 ->withIDs(array($workflow)) 37 ->executeOne(); 38 } 39 40 $field_list = PhabricatorCustomField::getObjectFields( 41 $task, 42 PhabricatorCustomField::ROLE_VIEW); 43 $field_list 44 ->setViewer($user) 45 ->readFieldsFromStorage($task); 46 47 $e_commit = ManiphestTaskHasCommitEdgeType::EDGECONST; 48 $e_dep_on = PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK; 49 $e_dep_by = PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK; 50 $e_rev = ManiphestTaskHasRevisionEdgeType::EDGECONST; 51 $e_mock = PhabricatorEdgeConfig::TYPE_TASK_HAS_MOCK; 52 53 $phid = $task->getPHID(); 54 55 $query = id(new PhabricatorEdgeQuery()) 56 ->withSourcePHIDs(array($phid)) 57 ->withEdgeTypes( 58 array( 59 $e_commit, 60 $e_dep_on, 61 $e_dep_by, 62 $e_rev, 63 $e_mock, 64 )); 65 $edges = idx($query->execute(), $phid); 66 $phids = array_fill_keys($query->getDestinationPHIDs(), true); 67 68 foreach ($task->getCCPHIDs() as $phid) { 69 $phids[$phid] = true; 70 } 71 foreach ($task->getProjectPHIDs() as $phid) { 72 $phids[$phid] = true; 73 } 74 if ($task->getOwnerPHID()) { 75 $phids[$task->getOwnerPHID()] = true; 76 } 77 $phids[$task->getAuthorPHID()] = true; 78 79 $attached = $task->getAttached(); 80 foreach ($attached as $type => $list) { 81 foreach ($list as $phid => $info) { 82 $phids[$phid] = true; 83 } 84 } 85 86 if ($parent_task) { 87 $phids[$parent_task->getPHID()] = true; 88 } 89 90 $phids = array_keys($phids); 91 92 $this->loadHandles($phids); 93 94 $handles = $this->getLoadedHandles(); 95 96 $context_bar = null; 97 98 if ($parent_task) { 99 $context_bar = new AphrontContextBarView(); 100 $context_bar->addButton(phutil_tag( 101 'a', 102 array( 103 'href' => '/maniphest/task/create/?parent='.$parent_task->getID(), 104 'class' => 'green button', 105 ), 106 pht('Create Another Subtask'))); 107 $context_bar->appendChild(hsprintf( 108 'Created a subtask of <strong>%s</strong>', 109 $this->getHandle($parent_task->getPHID())->renderLink())); 110 } else if ($workflow == 'create') { 111 $context_bar = new AphrontContextBarView(); 112 $context_bar->addButton(phutil_tag('label', array(), 'Create Another')); 113 $context_bar->addButton(phutil_tag( 114 'a', 115 array( 116 'href' => '/maniphest/task/create/?template='.$task->getID(), 117 'class' => 'green button', 118 ), 119 pht('Similar Task'))); 120 $context_bar->addButton(phutil_tag( 121 'a', 122 array( 123 'href' => '/maniphest/task/create/', 124 'class' => 'green button', 125 ), 126 pht('Empty Task'))); 127 $context_bar->appendChild(pht('New task created.')); 128 } 129 130 $engine = new PhabricatorMarkupEngine(); 131 $engine->setViewer($user); 132 $engine->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION); 133 134 $timeline = $this->buildTransactionTimeline( 135 $task, 136 new ManiphestTransactionQuery(), 137 $engine); 138 139 $resolution_types = ManiphestTaskStatus::getTaskStatusMap(); 140 141 $transaction_types = array( 142 PhabricatorTransactions::TYPE_COMMENT => pht('Comment'), 143 ManiphestTransaction::TYPE_STATUS => pht('Change Status'), 144 ManiphestTransaction::TYPE_OWNER => pht('Reassign / Claim'), 145 ManiphestTransaction::TYPE_CCS => pht('Add CCs'), 146 ManiphestTransaction::TYPE_PRIORITY => pht('Change Priority'), 147 ManiphestTransaction::TYPE_PROJECTS => pht('Associate Projects'), 148 ); 149 150 // Remove actions the user doesn't have permission to take. 151 152 $requires = array( 153 ManiphestTransaction::TYPE_OWNER => 154 ManiphestEditAssignCapability::CAPABILITY, 155 ManiphestTransaction::TYPE_PRIORITY => 156 ManiphestEditPriorityCapability::CAPABILITY, 157 ManiphestTransaction::TYPE_PROJECTS => 158 ManiphestEditProjectsCapability::CAPABILITY, 159 ManiphestTransaction::TYPE_STATUS => 160 ManiphestEditStatusCapability::CAPABILITY, 161 ); 162 163 foreach ($transaction_types as $type => $name) { 164 if (isset($requires[$type])) { 165 if (!$this->hasApplicationCapability($requires[$type])) { 166 unset($transaction_types[$type]); 167 } 168 } 169 } 170 171 // Don't show an option to change to the current status, or to change to 172 // the duplicate status explicitly. 173 unset($resolution_types[$task->getStatus()]); 174 unset($resolution_types[ManiphestTaskStatus::getDuplicateStatus()]); 175 176 // Don't show owner/priority changes for closed tasks, as they don't make 177 // much sense. 178 if ($task->isClosed()) { 179 unset($transaction_types[ManiphestTransaction::TYPE_PRIORITY]); 180 unset($transaction_types[ManiphestTransaction::TYPE_OWNER]); 181 } 182 183 $default_claim = array( 184 $user->getPHID() => $user->getUsername().' ('.$user->getRealName().')', 185 ); 186 187 $draft = id(new PhabricatorDraft())->loadOneWhere( 188 'authorPHID = %s AND draftKey = %s', 189 $user->getPHID(), 190 $task->getPHID()); 191 if ($draft) { 192 $draft_text = $draft->getDraft(); 193 } else { 194 $draft_text = null; 195 } 196 197 $comment_form = new AphrontFormView(); 198 $comment_form 199 ->setUser($user) 200 ->setWorkflow(true) 201 ->setAction('/maniphest/transaction/save/') 202 ->setEncType('multipart/form-data') 203 ->addHiddenInput('taskID', $task->getID()) 204 ->appendChild( 205 id(new AphrontFormSelectControl()) 206 ->setLabel(pht('Action')) 207 ->setName('action') 208 ->setOptions($transaction_types) 209 ->setID('transaction-action')) 210 ->appendChild( 211 id(new AphrontFormSelectControl()) 212 ->setLabel(pht('Status')) 213 ->setName('resolution') 214 ->setControlID('resolution') 215 ->setControlStyle('display: none') 216 ->setOptions($resolution_types)) 217 ->appendChild( 218 id(new AphrontFormTokenizerControl()) 219 ->setLabel(pht('Assign To')) 220 ->setName('assign_to') 221 ->setControlID('assign_to') 222 ->setControlStyle('display: none') 223 ->setID('assign-tokenizer') 224 ->setDisableBehavior(true)) 225 ->appendChild( 226 id(new AphrontFormTokenizerControl()) 227 ->setLabel(pht('CCs')) 228 ->setName('ccs') 229 ->setControlID('ccs') 230 ->setControlStyle('display: none') 231 ->setID('cc-tokenizer') 232 ->setDisableBehavior(true)) 233 ->appendChild( 234 id(new AphrontFormSelectControl()) 235 ->setLabel(pht('Priority')) 236 ->setName('priority') 237 ->setOptions($priority_map) 238 ->setControlID('priority') 239 ->setControlStyle('display: none') 240 ->setValue($task->getPriority())) 241 ->appendChild( 242 id(new AphrontFormTokenizerControl()) 243 ->setLabel(pht('Projects')) 244 ->setName('projects') 245 ->setControlID('projects') 246 ->setControlStyle('display: none') 247 ->setID('projects-tokenizer') 248 ->setDisableBehavior(true)) 249 ->appendChild( 250 id(new AphrontFormFileControl()) 251 ->setLabel(pht('File')) 252 ->setName('file') 253 ->setControlID('file') 254 ->setControlStyle('display: none')) 255 ->appendChild( 256 id(new PhabricatorRemarkupControl()) 257 ->setUser($user) 258 ->setLabel(pht('Comments')) 259 ->setName('comments') 260 ->setValue($draft_text) 261 ->setID('transaction-comments') 262 ->setUser($user)) 263 ->appendChild( 264 id(new AphrontFormSubmitControl()) 265 ->setValue(pht('Submit'))); 266 267 $control_map = array( 268 ManiphestTransaction::TYPE_STATUS => 'resolution', 269 ManiphestTransaction::TYPE_OWNER => 'assign_to', 270 ManiphestTransaction::TYPE_CCS => 'ccs', 271 ManiphestTransaction::TYPE_PRIORITY => 'priority', 272 ManiphestTransaction::TYPE_PROJECTS => 'projects', 273 ); 274 275 $projects_source = new PhabricatorProjectDatasource(); 276 $users_source = new PhabricatorPeopleDatasource(); 277 $mailable_source = new PhabricatorMetaMTAMailableDatasource(); 278 279 $tokenizer_map = array( 280 ManiphestTransaction::TYPE_PROJECTS => array( 281 'id' => 'projects-tokenizer', 282 'src' => $projects_source->getDatasourceURI(), 283 'placeholder' => $projects_source->getPlaceholderText(), 284 ), 285 ManiphestTransaction::TYPE_OWNER => array( 286 'id' => 'assign-tokenizer', 287 'src' => $users_source->getDatasourceURI(), 288 'value' => $default_claim, 289 'limit' => 1, 290 'placeholder' => $users_source->getPlaceholderText(), 291 ), 292 ManiphestTransaction::TYPE_CCS => array( 293 'id' => 'cc-tokenizer', 294 'src' => $mailable_source->getDatasourceURI(), 295 'placeholder' => $mailable_source->getPlaceholderText(), 296 ), 297 ); 298 299 // TODO: Initializing these behaviors for logged out users fatals things. 300 if ($user->isLoggedIn()) { 301 Javelin::initBehavior('maniphest-transaction-controls', array( 302 'select' => 'transaction-action', 303 'controlMap' => $control_map, 304 'tokenizers' => $tokenizer_map, 305 )); 306 307 Javelin::initBehavior('maniphest-transaction-preview', array( 308 'uri' => '/maniphest/transaction/preview/'.$task->getID().'/', 309 'preview' => 'transaction-preview', 310 'comments' => 'transaction-comments', 311 'action' => 'transaction-action', 312 'map' => $control_map, 313 'tokenizers' => $tokenizer_map, 314 )); 315 } 316 317 $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); 318 $comment_header = $is_serious 319 ? pht('Add Comment') 320 : pht('Weigh In'); 321 322 $preview_panel = phutil_tag_div( 323 'aphront-panel-preview', 324 phutil_tag( 325 'div', 326 array('id' => 'transaction-preview'), 327 phutil_tag_div( 328 'aphront-panel-preview-loading-text', 329 pht('Loading preview...')))); 330 331 $object_name = 'T'.$task->getID(); 332 $actions = $this->buildActionView($task); 333 334 $crumbs = $this->buildApplicationCrumbs() 335 ->addTextCrumb($object_name, '/'.$object_name) 336 ->setActionList($actions); 337 338 $header = $this->buildHeaderView($task); 339 $properties = $this->buildPropertyView( 340 $task, $field_list, $edges, $actions); 341 $description = $this->buildDescriptionView($task, $engine); 342 343 if (!$user->isLoggedIn()) { 344 // TODO: Eventually, everything should run through this. For now, we're 345 // only using it to get a consistent "Login to Comment" button. 346 $comment_box = id(new PhabricatorApplicationTransactionCommentView()) 347 ->setUser($user) 348 ->setRequestURI($request->getRequestURI()); 349 $preview_panel = null; 350 } else { 351 $comment_box = id(new PHUIObjectBoxView()) 352 ->setFlush(true) 353 ->setHeaderText($comment_header) 354 ->appendChild($comment_form); 355 $timeline->setQuoteTargetID('transaction-comments'); 356 $timeline->setQuoteRef($object_name); 357 } 358 359 $object_box = id(new PHUIObjectBoxView()) 360 ->setHeader($header) 361 ->addPropertyList($properties); 362 363 if ($description) { 364 $object_box->addPropertyList($description); 365 } 366 367 return $this->buildApplicationPage( 368 array( 369 $crumbs, 370 $context_bar, 371 $object_box, 372 $timeline, 373 $comment_box, 374 $preview_panel, 375 ), 376 array( 377 'title' => 'T'.$task->getID().' '.$task->getTitle(), 378 'pageObjects' => array($task->getPHID()), 379 )); 380 } 381 382 private function buildHeaderView(ManiphestTask $task) { 383 $view = id(new PHUIHeaderView()) 384 ->setHeader($task->getTitle()) 385 ->setUser($this->getRequest()->getUser()) 386 ->setPolicyObject($task); 387 388 $status = $task->getStatus(); 389 $status_name = ManiphestTaskStatus::renderFullDescription($status); 390 391 $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); 392 393 return $view; 394 } 395 396 397 private function buildActionView(ManiphestTask $task) { 398 $viewer = $this->getRequest()->getUser(); 399 $viewer_phid = $viewer->getPHID(); 400 $viewer_is_cc = in_array($viewer_phid, $task->getCCPHIDs()); 401 402 $id = $task->getID(); 403 $phid = $task->getPHID(); 404 405 $can_edit = PhabricatorPolicyFilter::hasCapability( 406 $viewer, 407 $task, 408 PhabricatorPolicyCapability::CAN_EDIT); 409 410 $view = id(new PhabricatorActionListView()) 411 ->setUser($viewer) 412 ->setObject($task) 413 ->setObjectURI($this->getRequest()->getRequestURI()); 414 415 $view->addAction( 416 id(new PhabricatorActionView()) 417 ->setName(pht('Edit Task')) 418 ->setIcon('fa-pencil') 419 ->setHref($this->getApplicationURI("/task/edit/{$id}/")) 420 ->setDisabled(!$can_edit) 421 ->setWorkflow(!$can_edit)); 422 423 if ($task->getOwnerPHID() === $viewer_phid) { 424 $view->addAction( 425 id(new PhabricatorActionView()) 426 ->setName(pht('Automatically Subscribed')) 427 ->setDisabled(true) 428 ->setIcon('fa-check-circle')); 429 } else { 430 $action = $viewer_is_cc ? 'rem' : 'add'; 431 $name = $viewer_is_cc ? pht('Unsubscribe') : pht('Subscribe'); 432 $icon = $viewer_is_cc ? 'fa-minus-circle' : 'fa-plus-circle'; 433 434 $view->addAction( 435 id(new PhabricatorActionView()) 436 ->setName($name) 437 ->setHref("/maniphest/subscribe/{$action}/{$id}/") 438 ->setRenderAsForm(true) 439 ->setUser($viewer) 440 ->setIcon($icon)); 441 } 442 443 $view->addAction( 444 id(new PhabricatorActionView()) 445 ->setName(pht('Merge Duplicates In')) 446 ->setHref("/search/attach/{$phid}/TASK/merge/") 447 ->setWorkflow(true) 448 ->setIcon('fa-compress') 449 ->setDisabled(!$can_edit) 450 ->setWorkflow(true)); 451 452 $view->addAction( 453 id(new PhabricatorActionView()) 454 ->setName(pht('Create Subtask')) 455 ->setHref($this->getApplicationURI("/task/create/?parent={$id}")) 456 ->setIcon('fa-level-down')); 457 458 $view->addAction( 459 id(new PhabricatorActionView()) 460 ->setName(pht('Edit Blocking Tasks')) 461 ->setHref("/search/attach/{$phid}/TASK/blocks/") 462 ->setWorkflow(true) 463 ->setIcon('fa-link') 464 ->setDisabled(!$can_edit) 465 ->setWorkflow(true)); 466 467 return $view; 468 } 469 470 private function buildPropertyView( 471 ManiphestTask $task, 472 PhabricatorCustomFieldList $field_list, 473 array $edges, 474 PhabricatorActionListView $actions) { 475 476 $viewer = $this->getRequest()->getUser(); 477 478 $view = id(new PHUIPropertyListView()) 479 ->setUser($viewer) 480 ->setObject($task) 481 ->setActionList($actions); 482 483 $view->addProperty( 484 pht('Assigned To'), 485 $task->getOwnerPHID() 486 ? $this->getHandle($task->getOwnerPHID())->renderLink() 487 : phutil_tag('em', array(), pht('None'))); 488 489 $view->addProperty( 490 pht('Priority'), 491 ManiphestTaskPriority::getTaskPriorityName($task->getPriority())); 492 493 $handles = $this->getLoadedHandles(); 494 $cc_handles = array_select_keys($handles, $task->getCCPHIDs()); 495 $subscriber_html = id(new SubscriptionListStringBuilder()) 496 ->setObjectPHID($task->getPHID()) 497 ->setHandles($cc_handles) 498 ->buildPropertyString(); 499 $view->addProperty(pht('Subscribers'), $subscriber_html); 500 501 $view->addProperty( 502 pht('Author'), 503 $this->getHandle($task->getAuthorPHID())->renderLink()); 504 505 $source = $task->getOriginalEmailSource(); 506 if ($source) { 507 $subject = '[T'.$task->getID().'] '.$task->getTitle(); 508 $view->addProperty( 509 pht('From Email'), 510 phutil_tag( 511 'a', 512 array( 513 'href' => 'mailto:'.$source.'?subject='.$subject, 514 ), 515 $source)); 516 } 517 518 $edge_types = array( 519 PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK 520 => pht('Blocks'), 521 PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK 522 => pht('Blocked By'), 523 ManiphestTaskHasRevisionEdgeType::EDGECONST 524 => pht('Differential Revisions'), 525 PhabricatorEdgeConfig::TYPE_TASK_HAS_MOCK 526 => pht('Pholio Mocks'), 527 ); 528 529 $revisions_commits = array(); 530 $handles = $this->getLoadedHandles(); 531 532 $commit_phids = array_keys( 533 $edges[ManiphestTaskHasCommitEdgeType::EDGECONST]); 534 if ($commit_phids) { 535 $commit_drev = PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV; 536 $drev_edges = id(new PhabricatorEdgeQuery()) 537 ->withSourcePHIDs($commit_phids) 538 ->withEdgeTypes(array($commit_drev)) 539 ->execute(); 540 541 foreach ($commit_phids as $phid) { 542 $revisions_commits[$phid] = $handles[$phid]->renderLink(); 543 $revision_phid = key($drev_edges[$phid][$commit_drev]); 544 $revision_handle = idx($handles, $revision_phid); 545 if ($revision_handle) { 546 $task_drev = ManiphestTaskHasRevisionEdgeType::EDGECONST; 547 unset($edges[$task_drev][$revision_phid]); 548 $revisions_commits[$phid] = hsprintf( 549 '%s / %s', 550 $revision_handle->renderLink($revision_handle->getName()), 551 $revisions_commits[$phid]); 552 } 553 } 554 } 555 556 foreach ($edge_types as $edge_type => $edge_name) { 557 if ($edges[$edge_type]) { 558 $view->addProperty( 559 $edge_name, 560 $this->renderHandlesForPHIDs(array_keys($edges[$edge_type]))); 561 } 562 } 563 564 if ($revisions_commits) { 565 $view->addProperty( 566 pht('Commits'), 567 phutil_implode_html(phutil_tag('br'), $revisions_commits)); 568 } 569 570 $attached = $task->getAttached(); 571 if (!is_array($attached)) { 572 $attached = array(); 573 } 574 575 $file_infos = idx($attached, PhabricatorFileFilePHIDType::TYPECONST); 576 if ($file_infos) { 577 $file_phids = array_keys($file_infos); 578 579 // TODO: These should probably be handles or something; clean this up 580 // as we sort out file attachments. 581 $files = id(new PhabricatorFileQuery()) 582 ->setViewer($viewer) 583 ->withPHIDs($file_phids) 584 ->execute(); 585 586 $file_view = new PhabricatorFileLinkListView(); 587 $file_view->setFiles($files); 588 589 $view->addProperty( 590 pht('Files'), 591 $file_view->render()); 592 } 593 594 $view->invokeWillRenderEvent(); 595 596 $field_list->appendFieldsToPropertyList( 597 $task, 598 $viewer, 599 $view); 600 601 return $view; 602 } 603 604 private function buildDescriptionView( 605 ManiphestTask $task, 606 PhabricatorMarkupEngine $engine) { 607 608 $section = null; 609 if (strlen($task->getDescription())) { 610 $section = new PHUIPropertyListView(); 611 $section->addSectionHeader( 612 pht('Description'), 613 PHUIPropertyListView::ICON_SUMMARY); 614 $section->addTextContent( 615 phutil_tag( 616 'div', 617 array( 618 'class' => 'phabricator-remarkup', 619 ), 620 $engine->getOutput($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION))); 621 } 622 623 return $section; 624 } 625 626 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |