[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/maniphest/controller/ -> ManiphestTaskDetailController.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1