protected function renderSingleTask(ManiphestTask $task) { $request = $this->getRequest(); $user = $request->getUser(); $phids = $task->getProjectPHIDs(); if ($task->getOwnerPHID()) { $phids[] = $task->getOwnerPHID(); } $handles = id(new PhabricatorHandleQuery())->setViewer($user)->withPHIDs($phids)->execute(); $view = id(new ManiphestTaskListView())->setUser($user)->setShowSubpriorityControls(!$request->getStr('ungrippable'))->setShowBatchControls(true)->setHandles($handles)->setTasks(array($task)); return $view; }
public static function updateTaskProjects(ManiphestTask $task) { $dao = new ManiphestTaskProject(); $conn = $dao->establishConnection('w'); $sql = array(); foreach ($task->getProjectPHIDs() as $project_phid) { $sql[] = qsprintf($conn, '(%s, %s)', $task->getPHID(), $project_phid); } queryfx($conn, 'DELETE FROM %T WHERE taskPHID = %s', $dao->getTableName(), $task->getPHID()); if ($sql) { queryfx($conn, 'INSERT INTO %T (taskPHID, projectPHID) VALUES %Q', $dao->getTableName(), implode(', ', $sql)); } }
public static function indexTask(ManiphestTask $task) { $doc = new PhabricatorSearchAbstractDocument(); $doc->setPHID($task->getPHID()); $doc->setDocumentType(PhabricatorPHIDConstants::PHID_TYPE_TASK); $doc->setDocumentTitle($task->getTitle()); $doc->setDocumentCreated($task->getDateCreated()); $doc->setDocumentModified($task->getDateModified()); $doc->addField(PhabricatorSearchField::FIELD_BODY, $task->getDescription()); $doc->addRelationship(PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR, $task->getAuthorPHID(), PhabricatorPHIDConstants::PHID_TYPE_USER, $task->getDateCreated()); if ($task->getStatus() == ManiphestTaskStatus::STATUS_OPEN) { $doc->addRelationship(PhabricatorSearchRelationship::RELATIONSHIP_OPEN, $task->getPHID(), PhabricatorPHIDConstants::PHID_TYPE_TASK, time()); } $transactions = id(new ManiphestTransaction())->loadAllWhere('taskID = %d', $task->getID()); $current_ccs = $task->getCCPHIDs(); $touches = array(); $owner = null; $ccs = array(); foreach ($transactions as $transaction) { if ($transaction->hasComments()) { $doc->addField(PhabricatorSearchField::FIELD_COMMENT, $transaction->getComments()); } $author = $transaction->getAuthorPHID(); // Record the most recent time they touched this object. $touches[$author] = $transaction->getDateCreated(); switch ($transaction->getTransactionType()) { case ManiphestTransactionType::TYPE_OWNER: $owner = $transaction; break; case ManiphestTransactionType::TYPE_CCS: // For users who are still CC'd, record the first time they were // added to CC. foreach ($transaction->getNewValue() as $added_cc) { if (in_array($added_cc, $current_ccs)) { if (empty($ccs[$added_cc])) { $ccs[$added_cc] = $transaction->getDateCreated(); } } } break; } } foreach ($task->getProjectPHIDs() as $phid) { $doc->addRelationship(PhabricatorSearchRelationship::RELATIONSHIP_PROJECT, $phid, PhabricatorPHIDConstants::PHID_TYPE_PROJ, $task->getDateModified()); // Bogus. } if ($owner && $owner->getNewValue()) { $doc->addRelationship(PhabricatorSearchRelationship::RELATIONSHIP_OWNER, $owner->getNewValue(), PhabricatorPHIDConstants::PHID_TYPE_USER, $owner->getDateCreated()); } else { $doc->addRelationship(PhabricatorSearchRelationship::RELATIONSHIP_OWNER, ManiphestTaskOwner::OWNER_UP_FOR_GRABS, PhabricatorPHIDConstants::PHID_TYPE_MAGIC, $owner ? $owner->getDateCreated() : $task->getDateCreated()); } foreach ($touches as $touch => $time) { $doc->addRelationship(PhabricatorSearchRelationship::RELATIONSHIP_TOUCH, $touch, PhabricatorPHIDConstants::PHID_TYPE_USER, $time); } // We need to load handles here since non-users may subscribe (mailing // lists, e.g.) $handles = id(new PhabricatorObjectHandleData(array_keys($ccs)))->loadHandles(); foreach ($ccs as $cc => $time) { $doc->addRelationship(PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER, $handles[$cc]->getPHID(), $handles[$cc]->getType(), $time); } self::reindexAbstractDocument($doc); }
private function buildCardResponse(ManiphestTask $task) { $controller = $this->getController(); $request = $controller->getRequest(); $viewer = $request->getViewer(); $column_phid = $request->getStr('columnPHID'); $order = $request->getStr('order'); $column = id(new PhabricatorProjectColumnQuery())->setViewer($viewer)->withPHIDs(array($column_phid))->executeOne(); if (!$column) { return new Aphront404Response(); } // If the workboard's project has been removed from the card's project // list, we are going to remove it from the board completely. $project_map = array_fuse($task->getProjectPHIDs()); $remove_card = empty($project_map[$column->getProjectPHID()]); $positions = id(new PhabricatorProjectColumnPositionQuery())->setViewer($viewer)->withColumns(array($column))->execute(); $task_phids = mpull($positions, 'getObjectPHID'); $column_tasks = id(new ManiphestTaskQuery())->setViewer($viewer)->withPHIDs($task_phids)->execute(); if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { // TODO: This is a little bit awkward, because PHP and JS use // slightly different sort order parameters to achieve the same // effect. It would be good to unify this a bit at some point. $sort_map = array(); foreach ($positions as $position) { $sort_map[$position->getObjectPHID()] = array(-$position->getSequence(), $position->getID()); } } else { $sort_map = mpull($column_tasks, 'getPrioritySortVector', 'getPHID'); } $data = array('removeFromBoard' => $remove_card, 'sortMap' => $sort_map); // TODO: This should just use HandlePool once we get through the EditEngine // transition. $owner = null; if ($task->getOwnerPHID()) { $owner = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs(array($task->getOwnerPHID()))->executeOne(); } $tasks = id(new ProjectBoardTaskCard())->setViewer($viewer)->setTask($task)->setOwner($owner)->setCanEdit(true)->getItem(); $payload = array('tasks' => $tasks, 'data' => $data); return id(new AphrontAjaxResponse())->setContent($payload); }
private function buildTransactions($actions, ManiphestTask $task) { $value_map = array(); $type_map = array('add_comment' => PhabricatorTransactions::TYPE_COMMENT, 'assign' => ManiphestTransaction::TYPE_OWNER, 'status' => ManiphestTransaction::TYPE_STATUS, 'priority' => ManiphestTransaction::TYPE_PRIORITY, 'add_project' => ManiphestTransaction::TYPE_PROJECTS, 'remove_project' => ManiphestTransaction::TYPE_PROJECTS, 'add_ccs' => ManiphestTransaction::TYPE_CCS, 'remove_ccs' => ManiphestTransaction::TYPE_CCS); $edge_edit_types = array('add_project' => true, 'remove_project' => true, 'add_ccs' => true, 'remove_ccs' => true); $xactions = array(); foreach ($actions as $action) { if (empty($type_map[$action['action']])) { throw new Exception("Unknown batch edit action '{$action}'!"); } $type = $type_map[$action['action']]; // Figure out the current value, possibly after modifications by other // batch actions of the same type. For example, if the user chooses to // "Add Comment" twice, we should add both comments. More notably, if the // user chooses "Remove Project..." and also "Add Project...", we should // avoid restoring the removed project in the second transaction. if (array_key_exists($type, $value_map)) { $current = $value_map[$type]; } else { switch ($type) { case PhabricatorTransactions::TYPE_COMMENT: $current = null; break; case ManiphestTransaction::TYPE_OWNER: $current = $task->getOwnerPHID(); break; case ManiphestTransaction::TYPE_STATUS: $current = $task->getStatus(); break; case ManiphestTransaction::TYPE_PRIORITY: $current = $task->getPriority(); break; case ManiphestTransaction::TYPE_PROJECTS: $current = $task->getProjectPHIDs(); break; case ManiphestTransaction::TYPE_CCS: $current = $task->getCCPHIDs(); break; } } // Check if the value is meaningful / provided, and normalize it if // necessary. This discards, e.g., empty comments and empty owner // changes. $value = $action['value']; switch ($type) { case PhabricatorTransactions::TYPE_COMMENT: if (!strlen($value)) { continue 2; } break; case ManiphestTransaction::TYPE_OWNER: if (empty($value)) { continue 2; } $value = head($value); if ($value === ManiphestTaskOwner::OWNER_UP_FOR_GRABS) { $value = null; } break; case ManiphestTransaction::TYPE_PROJECTS: if (empty($value)) { continue 2; } break; case ManiphestTransaction::TYPE_CCS: if (empty($value)) { continue 2; } break; } // If the edit doesn't change anything, go to the next action. This // check is only valid for changes like "owner", "status", etc, not // for edge edits, because we should still apply an edit like // "Remove Projects: A, B" to a task with projects "A, B". if (empty($edge_edit_types[$action['action']])) { if ($value == $current) { continue; } } // Apply the value change; for most edits this is just replacement, but // some need to merge the current and edited values (add/remove project). switch ($type) { case PhabricatorTransactions::TYPE_COMMENT: if (strlen($current)) { $value = $current . "\n\n" . $value; } break; case ManiphestTransaction::TYPE_PROJECTS: case ManiphestTransaction::TYPE_CCS: $remove_actions = array('remove_project' => true, 'remove_ccs' => true); $is_remove = isset($remove_actions[$action['action']]); $current = array_fill_keys($current, true); $value = array_fill_keys($value, true); $new = $current; $did_something = false; if ($is_remove) { foreach ($value as $phid => $ignored) { if (isset($new[$phid])) { unset($new[$phid]); $did_something = true; } } } else { foreach ($value as $phid => $ignored) { if (empty($new[$phid])) { $new[$phid] = true; $did_something = true; } } } if (!$did_something) { continue 2; } $value = array_keys($new); break; } $value_map[$type] = $value; } $template = new ManiphestTransaction(); foreach ($value_map as $type => $value) { $xaction = clone $template; $xaction->setTransactionType($type); switch ($type) { case PhabricatorTransactions::TYPE_COMMENT: $xaction->attachComment(id(new ManiphestTransactionComment())->setContent($value)); break; case ManiphestTransaction::TYPE_PROJECTS: // TODO: Clean this mess up. $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xaction->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $project_type)->setNewValue(array('=' => array_fuse($value))); break; default: $xaction->setNewValue($value); break; } $xactions[] = $xaction; } return $xactions; }
private function buildTransactions($actions, ManiphestTask $task) { $value_map = array(); $type_map = array('add_comment' => ManiphestTransactionType::TYPE_NONE, 'assign' => ManiphestTransactionType::TYPE_OWNER, 'status' => ManiphestTransactionType::TYPE_STATUS, 'priority' => ManiphestTransactionType::TYPE_PRIORITY, 'add_project' => ManiphestTransactionType::TYPE_PROJECTS, 'remove_project' => ManiphestTransactionType::TYPE_PROJECTS); $xactions = array(); foreach ($actions as $action) { if (empty($type_map[$action['action']])) { throw new Exception("Unknown batch edit action '{$action}'!"); } $type = $type_map[$action['action']]; // Figure out the current value, possibly after modifications by other // batch actions of the same type. For example, if the user chooses to // "Add Comment" twice, we should add both comments. More notably, if the // user chooses "Remove Project..." and also "Add Project...", we should // avoid restoring the removed project in the second transaction. if (array_key_exists($type, $value_map)) { $current = $value_map[$type]; } else { switch ($type) { case ManiphestTransactionType::TYPE_NONE: $current = null; break; case ManiphestTransactionType::TYPE_OWNER: $current = $task->getOwnerPHID(); break; case ManiphestTransactionType::TYPE_STATUS: $current = $task->getStatus(); break; case ManiphestTransactionType::TYPE_PRIORITY: $current = $task->getPriority(); break; case ManiphestTransactionType::TYPE_PROJECTS: $current = $task->getProjectPHIDs(); break; } } // Check if the value is meaningful / provided, and normalize it if // necessary. This discards, e.g., empty comments and empty owner // changes. $value = $action['value']; switch ($type) { case ManiphestTransactionType::TYPE_NONE: if (!strlen($value)) { continue 2; } break; case ManiphestTransactionType::TYPE_OWNER: if (empty($value)) { continue 2; } $value = head($value); if ($value === ManiphestTaskOwner::OWNER_UP_FOR_GRABS) { $value = null; } break; case ManiphestTransactionType::TYPE_PROJECTS: if (empty($value)) { continue 2; } break; } // If the edit doesn't change anything, go to the next action. if ($value == $current) { continue; } // Apply the value change; for most edits this is just replacement, but // some need to merge the current and edited values (add/remove project). switch ($type) { case ManiphestTransactionType::TYPE_NONE: if (strlen($current)) { $value = $current . "\n\n" . $value; } break; case ManiphestTransactionType::TYPE_PROJECTS: $is_remove = $action['action'] == 'remove_project'; $current = array_fill_keys($current, true); $value = array_fill_keys($value, true); $new = $current; $did_something = false; if ($is_remove) { foreach ($value as $phid => $ignored) { if (isset($new[$phid])) { unset($new[$phid]); $did_something = true; } } } else { foreach ($value as $phid => $ignored) { if (empty($new[$phid])) { $new[$phid] = true; $did_something = true; } } } if (!$did_something) { continue 2; } $value = array_keys($new); break; } $value_map[$type] = $value; } $template = new ManiphestTransaction(); $template->setAuthorPHID($this->getRequest()->getUser()->getPHID()); // TODO: Set content source to "batch edit". foreach ($value_map as $type => $value) { $xaction = clone $template; $xaction->setTransactionType($type); switch ($type) { case ManiphestTransactionType::TYPE_NONE: $xaction->setComments($value); break; default: $xaction->setNewValue($value); break; } $xactions[] = $xaction; } return $xactions; }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $files = array(); $parent_task = null; $template_id = null; if ($this->id) { $task = id(new ManiphestTask())->load($this->id); if (!$task) { return new Aphront404Response(); } } else { $task = new ManiphestTask(); $task->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE); $task->setAuthorPHID($user->getPHID()); // These allow task creation with defaults. if (!$request->isFormPost()) { $task->setTitle($request->getStr('title')); $default_projects = $request->getStr('projects'); if ($default_projects) { $task->setProjectPHIDs(explode(';', $default_projects)); } } $file_phids = $request->getArr('files', array()); if (!$file_phids) { // Allow a single 'file' key instead, mostly since Mac OS X urlencodes // square brackets in URLs when passed to 'open', so you can't 'open' // a URL like '?files[]=xyz' and have PHP interpret it correctly. $phid = $request->getStr('file'); if ($phid) { $file_phids = array($phid); } } if ($file_phids) { $files = id(new PhabricatorFile())->loadAllWhere('phid IN (%Ls)', $file_phids); } $template_id = $request->getInt('template'); // You can only have a parent task if you're creating a new task. $parent_id = $request->getInt('parent'); if ($parent_id) { $parent_task = id(new ManiphestTask())->load($parent_id); } } $errors = array(); $e_title = true; $extensions = ManiphestTaskExtensions::newExtensions(); $aux_fields = $extensions->getAuxiliaryFieldSpecifications(); if ($request->isFormPost()) { $changes = array(); $new_title = $request->getStr('title'); $new_desc = $request->getStr('description'); $new_status = $request->getStr('status'); $workflow = ''; if ($task->getID()) { if ($new_title != $task->getTitle()) { $changes[ManiphestTransactionType::TYPE_TITLE] = $new_title; } if ($new_desc != $task->getDescription()) { $changes[ManiphestTransactionType::TYPE_DESCRIPTION] = $new_desc; } if ($new_status != $task->getStatus()) { $changes[ManiphestTransactionType::TYPE_STATUS] = $new_status; } } else { $task->setTitle($new_title); $task->setDescription($new_desc); $changes[ManiphestTransactionType::TYPE_STATUS] = ManiphestTaskStatus::STATUS_OPEN; $workflow = 'create'; } $owner_tokenizer = $request->getArr('assigned_to'); $owner_phid = reset($owner_tokenizer); if (!strlen($new_title)) { $e_title = 'Required'; $errors[] = 'Title is required.'; } foreach ($aux_fields as $aux_field) { $aux_field->setValueFromRequest($request); if ($aux_field->isRequired() && !strlen($aux_field->getValue())) { $errors[] = $aux_field->getLabel() . ' is required.'; $aux_field->setError('Required'); } if (strlen($aux_field->getValue())) { try { $aux_field->validate(); } catch (Exception $e) { $errors[] = $e->getMessage(); $aux_field->setError('Invalid'); } } } if ($errors) { $task->setPriority($request->getInt('priority')); $task->setOwnerPHID($owner_phid); $task->setCCPHIDs($request->getArr('cc')); $task->setProjectPHIDs($request->getArr('projects')); } else { if ($request->getInt('priority') != $task->getPriority()) { $changes[ManiphestTransactionType::TYPE_PRIORITY] = $request->getInt('priority'); } if ($owner_phid != $task->getOwnerPHID()) { $changes[ManiphestTransactionType::TYPE_OWNER] = $owner_phid; } if ($request->getArr('cc') != $task->getCCPHIDs()) { $changes[ManiphestTransactionType::TYPE_CCS] = $request->getArr('cc'); } $new_proj_arr = $request->getArr('projects'); $new_proj_arr = array_values($new_proj_arr); sort($new_proj_arr); $cur_proj_arr = $task->getProjectPHIDs(); $cur_proj_arr = array_values($cur_proj_arr); sort($cur_proj_arr); if ($new_proj_arr != $cur_proj_arr) { $changes[ManiphestTransactionType::TYPE_PROJECTS] = $new_proj_arr; } if ($files) { $file_map = mpull($files, 'getPHID'); $file_map = array_fill_keys($file_map, array()); $changes[ManiphestTransactionType::TYPE_ATTACH] = array(PhabricatorPHIDConstants::PHID_TYPE_FILE => $file_map); } $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_WEB, array('ip' => $request->getRemoteAddr())); $template = new ManiphestTransaction(); $template->setAuthorPHID($user->getPHID()); $template->setContentSource($content_source); $transactions = array(); foreach ($changes as $type => $value) { $transaction = clone $template; $transaction->setTransactionType($type); $transaction->setNewValue($value); $transactions[] = $transaction; } if ($aux_fields) { $task->loadAndAttachAuxiliaryAttributes(); foreach ($aux_fields as $aux_field) { $transaction = clone $template; $transaction->setTransactionType(ManiphestTransactionType::TYPE_AUXILIARY); $aux_key = $aux_field->getAuxiliaryKey(); $transaction->setMetadataValue('aux:key', $aux_key); $transaction->setNewValue($aux_field->getValueForStorage()); $transactions[] = $transaction; } } if ($transactions) { $is_new = !$task->getID(); $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, array('task' => $task, 'new' => $is_new, 'transactions' => $transactions)); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); $task = $event->getValue('task'); $transactions = $event->getValue('transactions'); $editor = new ManiphestTransactionEditor(); $editor->setAuxiliaryFields($aux_fields); $editor->applyTransactions($task, $transactions); $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, array('task' => $task, 'new' => $is_new, 'transactions' => $transactions)); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); } if ($parent_task) { $type_task = PhabricatorPHIDConstants::PHID_TYPE_TASK; // NOTE: It's safe to simply apply this transaction without doing // cycle detection because we know the new task has no children. $new_value = $parent_task->getAttached(); $new_value[$type_task][$task->getPHID()] = array(); $parent_xaction = clone $template; $attach_type = ManiphestTransactionType::TYPE_ATTACH; $parent_xaction->setTransactionType($attach_type); $parent_xaction->setNewValue($new_value); $editor = new ManiphestTransactionEditor(); $editor->setAuxiliaryFields($aux_fields); $editor->applyTransactions($parent_task, array($parent_xaction)); $workflow = $parent_task->getID(); } $redirect_uri = '/T' . $task->getID(); if ($workflow) { $redirect_uri .= '?workflow=' . $workflow; } return id(new AphrontRedirectResponse())->setURI($redirect_uri); } } else { if (!$task->getID()) { $task->setCCPHIDs(array($user->getPHID())); if ($template_id) { $template_task = id(new ManiphestTask())->load($template_id); if ($template_task) { $task->setCCPHIDs($template_task->getCCPHIDs()); $task->setProjectPHIDs($template_task->getProjectPHIDs()); $task->setOwnerPHID($template_task->getOwnerPHID()); } } } } $phids = array_merge(array($task->getOwnerPHID()), $task->getCCPHIDs(), $task->getProjectPHIDs()); if ($parent_task) { $phids[] = $parent_task->getPHID(); } $phids = array_filter($phids); $phids = array_unique($phids); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles($phids); $tvalues = mpull($handles, 'getFullName', 'getPHID'); $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); $error_view->setTitle('Form Errors'); } $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); if ($task->getOwnerPHID()) { $assigned_value = array($task->getOwnerPHID() => $handles[$task->getOwnerPHID()]->getFullName()); } else { $assigned_value = array(); } if ($task->getCCPHIDs()) { $cc_value = array_select_keys($tvalues, $task->getCCPHIDs()); } else { $cc_value = array(); } if ($task->getProjectPHIDs()) { $projects_value = array_select_keys($tvalues, $task->getProjectPHIDs()); } else { $projects_value = array(); } $cancel_id = nonempty($task->getID(), $template_id); if ($cancel_id) { $cancel_uri = '/T' . $cancel_id; } else { $cancel_uri = '/maniphest/'; } if ($task->getID()) { $button_name = 'Save Task'; $header_name = 'Edit Task'; } else { if ($parent_task) { $cancel_uri = '/T' . $parent_task->getID(); $button_name = 'Create Task'; $header_name = 'Create New Subtask'; } else { $button_name = 'Create Task'; $header_name = 'Create New Task'; } } require_celerity_resource('maniphest-task-edit-css'); $project_tokenizer_id = celerity_generate_unique_node_id(); $form = new AphrontFormView(); $form->setUser($user)->setAction($request->getRequestURI()->getPath())->addHiddenInput('template', $template_id); if ($parent_task) { $form->appendChild(id(new AphrontFormStaticControl())->setLabel('Parent Task')->setValue($handles[$parent_task->getPHID()]->getFullName()))->addHiddenInput('parent', $parent_task->getID()); } $form->appendChild(id(new AphrontFormTextAreaControl())->setLabel('Title')->setName('title')->setError($e_title)->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)->setValue($task->getTitle())); if ($task->getID()) { // Only show this in "edit" mode, not "create" mode, since creating a // non-open task is kind of silly and it would just clutter up the // "create" interface. $form->appendChild(id(new AphrontFormSelectControl())->setLabel('Status')->setName('status')->setValue($task->getStatus())->setOptions(ManiphestTaskStatus::getTaskStatusMap())); } $form->appendChild(id(new AphrontFormTokenizerControl())->setLabel('Assigned To')->setName('assigned_to')->setValue($assigned_value)->setUser($user)->setDatasource('/typeahead/common/users/')->setLimit(1))->appendChild(id(new AphrontFormTokenizerControl())->setLabel('CC')->setName('cc')->setValue($cc_value)->setUser($user)->setDatasource('/typeahead/common/mailable/'))->appendChild(id(new AphrontFormSelectControl())->setLabel('Priority')->setName('priority')->setOptions($priority_map)->setValue($task->getPriority()))->appendChild(id(new AphrontFormTokenizerControl())->setLabel('Projects')->setName('projects')->setValue($projects_value)->setID($project_tokenizer_id)->setCaption(javelin_render_tag('a', array('href' => '/project/create/', 'mustcapture' => true, 'sigil' => 'project-create'), 'Create New Project'))->setDatasource('/typeahead/common/projects/')); if ($aux_fields) { if (!$request->isFormPost()) { $task->loadAndAttachAuxiliaryAttributes(); foreach ($aux_fields as $aux_field) { $aux_key = $aux_field->getAuxiliaryKey(); $value = $task->getAuxiliaryAttribute($aux_key); $aux_field->setValueFromStorage($value); } } foreach ($aux_fields as $aux_field) { if ($aux_field->isRequired() && !$aux_field->getError() && !$aux_field->getValue()) { $aux_field->setError(true); } $aux_control = $aux_field->renderControl(); $form->appendChild($aux_control); } } require_celerity_resource('aphront-error-view-css'); Javelin::initBehavior('maniphest-project-create', array('tokenizerID' => $project_tokenizer_id)); if ($files) { $file_display = array(); foreach ($files as $file) { $file_display[] = phutil_escape_html($file->getName()); } $file_display = implode('<br />', $file_display); $form->appendChild(id(new AphrontFormMarkupControl())->setLabel('Files')->setValue($file_display)); foreach ($files as $ii => $file) { $form->addHiddenInput('files[' . $ii . ']', $file->getPHID()); } } $email_create = PhabricatorEnv::getEnvConfig('metamta.maniphest.public-create-email'); $email_hint = null; if (!$task->getID() && $email_create) { $email_hint = 'You can also create tasks by sending an email to: ' . '<tt>' . phutil_escape_html($email_create) . '</tt>'; } $panel_id = celerity_generate_unique_node_id(); $form->appendChild(id(new AphrontFormTextAreaControl())->setLabel('Description')->setName('description')->setID('description-textarea')->setCaption($email_hint)->setValue($task->getDescription())); if (!$task->getID()) { $form->appendChild(id(new AphrontFormDragAndDropUploadControl())->setLabel('Attached Files')->setName('files')->setDragAndDropTarget($panel_id)->setActivatedClass('aphront-panel-view-drag-and-drop')); } $form->appendChild(id(new AphrontFormSubmitControl())->addCancelButton($cancel_uri)->setValue($button_name)); $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_FULL); $panel->setHeader($header_name); $panel->setID($panel_id); $panel->appendChild($form); $description_preview_panel = '<div class="aphront-panel-preview aphront-panel-preview-full"> <div class="maniphest-description-preview-header"> Description Preview </div> <div id="description-preview"> <div class="aphront-panel-preview-loading-text"> Loading preview... </div> </div> </div>'; Javelin::initBehavior('maniphest-description-preview', array('preview' => 'description-preview', 'textarea' => 'description-textarea', 'uri' => '/maniphest/task/descriptionpreview/')); return $this->buildStandardPageResponse(array($error_view, $panel, $description_preview_panel), array('title' => $header_name)); }
private function buildCardResponse(ManiphestTask $task) { $controller = $this->getController(); $request = $controller->getRequest(); $viewer = $request->getViewer(); $column_phid = $request->getStr('columnPHID'); $order = $request->getStr('order'); $column = id(new PhabricatorProjectColumnQuery())->setViewer($viewer)->withPHIDs(array($column_phid))->executeOne(); if (!$column) { return new Aphront404Response(); } // If the workboard's project and all descendant projects have been removed // from the card's project list, we are going to remove it from the board // completely. // TODO: If the user did something sneaky and changed a subproject, we'll // currently leave the card where it was but should really move it to the // proper new column. $board_phid = $column->getProjectPHID(); $descendant_projects = id(new PhabricatorProjectQuery())->setViewer($viewer)->withAncestorProjectPHIDs(array($column->getProjectPHID()))->execute(); $board_phids = mpull($descendant_projects, 'getPHID', 'getPHID'); $board_phids[$board_phid] = $board_phid; $project_map = array_fuse($task->getProjectPHIDs()); $remove_card = !array_intersect_key($board_phids, $project_map); // TODO: Maybe the caller should pass a list of visible task PHIDs so we // know which ones we need to reorder? This is a HUGE overfetch. $objects = id(new ManiphestTaskQuery())->setViewer($viewer)->withEdgeLogicPHIDs(PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_ANCESTOR, array($board_phids))->setViewer($viewer)->execute(); $objects = mpull($objects, null, 'getPHID'); $layout_engine = id(new PhabricatorBoardLayoutEngine())->setViewer($viewer)->setBoardPHIDs(array($board_phid))->setObjectPHIDs(array_keys($objects))->executeLayout(); $positions = $layout_engine->getColumnObjectPositions($board_phid, $column_phid); $column_phids = $layout_engine->getColumnObjectPHIDs($board_phid, $column_phid); $column_tasks = array_select_keys($objects, $column_phids); if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { // TODO: This is a little bit awkward, because PHP and JS use // slightly different sort order parameters to achieve the same // effect. It would be good to unify this a bit at some point. $sort_map = array(); foreach ($positions as $position) { $sort_map[$position->getObjectPHID()] = array(-$position->getSequence(), $position->getID()); } } else { $sort_map = mpull($column_tasks, 'getPrioritySortVector', 'getPHID'); } $data = array('removeFromBoard' => $remove_card, 'sortMap' => $sort_map); $rendering_engine = id(new PhabricatorBoardRenderingEngine())->setViewer($viewer)->setObjects(array($task))->setExcludedProjectPHIDs($board_phids); $card = $rendering_engine->renderCard($task->getPHID()); $item = $card->getItem(); $item->addClass('phui-workcard'); $payload = array('tasks' => $item, 'data' => $data); return id(new AphrontAjaxResponse())->setContent(array('tasks' => $item, 'data' => $data)); }
private function buildCardResponse(ManiphestTask $task) { $controller = $this->getController(); $request = $controller->getRequest(); $viewer = $request->getViewer(); $column_phid = $request->getStr('columnPHID'); $order = $request->getStr('order'); $column = id(new PhabricatorProjectColumnQuery())->setViewer($viewer)->withPHIDs(array($column_phid))->executeOne(); if (!$column) { return new Aphront404Response(); } // If the workboard's project and all descendant projects have been removed // from the card's project list, we are going to remove it from the board // completely. // TODO: If the user did something sneaky and changed a subproject, we'll // currently leave the card where it was but should really move it to the // proper new column. $descendant_projects = id(new PhabricatorProjectQuery())->setViewer($viewer)->withAncestorProjectPHIDs(array($column->getProjectPHID()))->execute(); $board_phids = mpull($descendant_projects, 'getPHID', 'getPHID'); $board_phids[$column->getProjectPHID()] = $column->getProjectPHID(); $project_map = array_fuse($task->getProjectPHIDs()); $remove_card = !array_intersect_key($board_phids, $project_map); $positions = id(new PhabricatorProjectColumnPositionQuery())->setViewer($viewer)->withBoardPHIDs(array($column->getProjectPHID()))->withColumnPHIDs(array($column->getPHID()))->execute(); $task_phids = mpull($positions, 'getObjectPHID'); $column_tasks = id(new ManiphestTaskQuery())->setViewer($viewer)->withPHIDs($task_phids)->needProjectPHIDs(true)->execute(); if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { // TODO: This is a little bit awkward, because PHP and JS use // slightly different sort order parameters to achieve the same // effect. It would be good to unify this a bit at some point. $sort_map = array(); foreach ($positions as $position) { $sort_map[$position->getObjectPHID()] = array(-$position->getSequence(), $position->getID()); } } else { $sort_map = mpull($column_tasks, 'getPrioritySortVector', 'getPHID'); } $data = array('removeFromBoard' => $remove_card, 'sortMap' => $sort_map); // TODO: This should just use HandlePool once we get through the EditEngine // transition. $owner = null; if ($task->getOwnerPHID()) { $owner = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs(array($task->getOwnerPHID()))->executeOne(); } $handle_phids = $task->getProjectPHIDs(); $handle_phids = array_fuse($handle_phids); $handle_phids = array_diff_key($handle_phids, $board_phids); $project_handles = $viewer->loadHandles($handle_phids); $project_handles = iterator_to_array($project_handles); $projects = $request->getArr('projectPHIDs'); $project = $this->getSprintProjectforTask($viewer, $projects); $tasks = id(new SprintBoardTaskCard())->setViewer($viewer)->setProject($project)->setProjectHandles($project_handles)->setTask($task)->setOwner($owner)->setCanEdit(true)->getItem(); $tasks->addClass('phui-workcard'); $payload = array('tasks' => $tasks, 'data' => $data); return id(new AphrontAjaxResponse())->setContent($payload); }
private function publishFeedStory(ManiphestTask $task, array $transactions) { assert_instances_of($transactions, 'ManiphestTransaction'); $actions = array(ManiphestAction::ACTION_UPDATE); $comments = null; foreach ($transactions as $transaction) { if ($transaction->hasComments()) { $comments = $transaction->getComments(); } switch ($transaction->getTransactionType()) { case ManiphestTransactionType::TYPE_OWNER: $actions[] = ManiphestAction::ACTION_ASSIGN; break; case ManiphestTransactionType::TYPE_STATUS: if ($task->getStatus() != ManiphestTaskStatus::STATUS_OPEN) { $actions[] = ManiphestAction::ACTION_CLOSE; } else { if ($this->isCreate($transactions)) { $actions[] = ManiphestAction::ACTION_CREATE; } } break; default: break; } } $action_type = ManiphestAction::selectStrongestAction($actions); $owner_phid = $task->getOwnerPHID(); $actor_phid = head($transactions)->getAuthorPHID(); $author_phid = $task->getAuthorPHID(); id(new PhabricatorFeedStoryPublisher())->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_MANIPHEST)->setStoryData(array('taskPHID' => $task->getPHID(), 'transactionIDs' => mpull($transactions, 'getID'), 'ownerPHID' => $owner_phid, 'action' => $action_type, 'comments' => $comments, 'description' => $task->getDescription()))->setStoryTime(time())->setStoryAuthorPHID($actor_phid)->setRelatedPHIDs(array_merge(array_filter(array($task->getPHID(), $author_phid, $actor_phid, $owner_phid)), $task->getProjectPHIDs()))->publish(); }