protected function execute(ConduitAPIRequest $request) { $task = new ManiphestTask(); $task->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE); $task->setAuthorPHID($request->getUser()->getPHID()); $task->setTitle((string) $request->getValue('title')); $task->setDescription((string) $request->getValue('description')); $changes = array(); $changes[ManiphestTransactionType::TYPE_STATUS] = ManiphestTaskStatus::STATUS_OPEN; $priority = $request->getValue('priority'); if ($priority !== null) { $changes[ManiphestTransactionType::TYPE_PRIORITY] = $priority; } $owner_phid = $request->getValue('ownerPHID'); if ($owner_phid !== null) { $changes[ManiphestTransactionType::TYPE_OWNER] = $owner_phid; } $ccs = $request->getValue('ccPHIDs'); if ($ccs !== null) { $changes[ManiphestTransactionType::TYPE_CCS] = $ccs; } $project_phids = $request->getValue('projectPHIDs'); if ($project_phids !== null) { $changes[ManiphestTransactionType::TYPE_PROJECTS] = $project_phids; } $file_phids = $request->getValue('filePHIDs'); if ($file_phids !== null) { $file_map = array_fill_keys($file_phids, true); $changes[ManiphestTransactionType::TYPE_ATTACH] = array(PhabricatorPHIDConstants::PHID_TYPE_FILE => $file_map); } $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_CONDUIT, array()); $template = new ManiphestTransaction(); $template->setContentSource($content_source); $template->setAuthorPHID($request->getUser()->getPHID()); $transactions = array(); foreach ($changes as $type => $value) { $transaction = clone $template; $transaction->setTransactionType($type); $transaction->setNewValue($value); $transactions[] = $transaction; } $editor = new ManiphestTransactionEditor(); $editor->applyTransactions($task, $transactions); return $this->buildTaskInfoDictionary($task); }
public function processRequest() { $request = $this->getRequest(); if (!$request->validateCSRF()) { return new Aphront403Response(); } $task = id(new ManiphestTask())->load($request->getInt('task')); if (!$task) { return new Aphront404Response(); } if ($request->getInt('after')) { $after_task = id(new ManiphestTask())->load($request->getInt('after')); if (!$after_task) { return new Aphront404Response(); } $after_pri = $after_task->getPriority(); $after_sub = $after_task->getSubpriority(); } else { $after_pri = $request->getInt('priority'); $after_sub = null; } $new_sub = ManiphestTransactionEditor::getNextSubpriority($after_pri, $after_sub); if ($after_pri != $task->getPriority()) { $xaction = new ManiphestTransaction(); $xaction->setAuthorPHID($request->getUser()->getPHID()); // TODO: Content source? $xaction->setTransactionType(ManiphestTransactionType::TYPE_PRIORITY); $xaction->setNewValue($after_pri); $editor = new ManiphestTransactionEditor(); $editor->applyTransactions($task, array($xaction)); } $task->setSubpriority($new_sub); $task->save(); $pri_class = ManiphestTaskSummaryView::getPriorityClass($task->getPriority()); $class = 'maniphest-task-handle maniphest-active-handle ' . $pri_class; $response = array('className' => $class); return id(new AphrontAjaxResponse())->setContent($response); }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $comments = $request->getStr('comments'); $task = id(new ManiphestTask())->load($this->id); if (!$task) { return new Aphront404Response(); } $draft = id(new PhabricatorDraft())->loadOneWhere('authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if (!$draft) { $draft = new PhabricatorDraft(); $draft->setAuthorPHID($user->getPHID()); $draft->setDraftKey($task->getPHID()); } $draft->setDraft($comments); $draft->save(); $phids = array($user->getPHID()); $action = $request->getStr('action'); $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID()); $transaction->setComments($comments); $transaction->setTransactionType($action); $value = $request->getStr('value'); switch ($action) { case ManiphestTransactionType::TYPE_OWNER: if (!$value) { $value = $user->getPHID(); } $phids[] = $value; break; case ManiphestTransactionType::TYPE_PRIORITY: $transaction->setOldValue($task->getPriority()); break; } $transaction->setNewValue($value); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $transactions = array(); $transactions[] = $transaction; $engine = PhabricatorMarkupEngine::newManiphestMarkupEngine(); $transaction_view = new ManiphestTransactionListView(); $transaction_view->setTransactions($transactions); $transaction_view->setHandles($handles); $transaction_view->setUser($user); $transaction_view->setMarkupEngine($engine); $transaction_view->setPreview(true); return id(new AphrontAjaxResponse())->setContent($transaction_view->render()); }
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)); }
<?php $table = new ManiphestTransaction(); $conn_w = $table->establishConnection('w'); echo pht("Converting Maniphest project transactions to modern edge transactions...\n"); $metadata = array('edge:type' => PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); foreach (new LiskMigrationIterator($table) as $txn) { if ($txn->getTransactionType() != 'projects') { continue; } $old_value = mig20141222_build_edge_data($txn->getOldValue(), $txn->getObjectPHID()); $new_value = mig20141222_build_edge_data($txn->getNewValue(), $txn->getObjectPHID()); queryfx($conn_w, 'UPDATE %T SET ' . 'transactionType = %s, oldValue = %s, newValue = %s, metaData = %s ' . 'WHERE id = %d', $table->getTableName(), PhabricatorTransactions::TYPE_EDGE, json_encode($old_value), json_encode($new_value), json_encode($metadata), $txn->getID()); } echo pht('Done.') . "\n"; function mig20141222_build_edge_data($project_phids, $task_phid) { $edge_data = array(); // See T9464. If we didn't get a proper array value out of the transaction, // just return an empty value so we can move forward. if (!is_array($project_phids)) { return $edge_data; } foreach ($project_phids as $project_phid) { if (!is_scalar($project_phid)) { continue; } $edge_data[$project_phid] = array('src' => $task_phid, 'type' => PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 'dst' => $project_phid); } return $edge_data; }
<?php $table = new ManiphestTransaction(); $conn_w = $table->establishConnection('w'); echo pht("Converting Maniphest CC transactions to modern " . "subscriber transactions...\n"); foreach (new LiskMigrationIterator($table) as $txn) { // ManiphestTransaction::TYPE_CCS if ($txn->getTransactionType() == 'ccs') { queryfx($conn_w, 'UPDATE %T SET transactionType = %s WHERE id = %d', $table->getTableName(), PhabricatorTransactions::TYPE_SUBSCRIBERS, $txn->getID()); } } echo pht('Done.') . "\n";
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $comments = $request->getStr('comments'); $task = id(new ManiphestTask())->load($this->id); if (!$task) { return new Aphront404Response(); } $draft = id(new PhabricatorDraft())->loadOneWhere('authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if (!$draft) { $draft = new PhabricatorDraft(); $draft->setAuthorPHID($user->getPHID()); $draft->setDraftKey($task->getPHID()); } $draft->setDraft($comments); $draft->save(); $action = $request->getStr('action'); $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID()); $transaction->setComments($comments); $transaction->setTransactionType($action); $value = $request->getStr('value'); // grab phids for handles and set transaction values based on action and // value (empty or control-specific format) coming in from the wire switch ($action) { case ManiphestTransactionType::TYPE_PRIORITY: $transaction->setOldValue($task->getPriority()); $transaction->setNewValue($value); break; case ManiphestTransactionType::TYPE_OWNER: if ($value) { $value = current(json_decode($value)); $phids = array($value); } else { $phids = array(); } $transaction->setNewValue($value); break; case ManiphestTransactionType::TYPE_CCS: if ($value) { $value = json_decode($value); $phids = $value; foreach ($task->getCCPHIDs() as $cc_phid) { $phids[] = $cc_phid; $value[] = $cc_phid; } $transaction->setNewValue($value); } else { $phids = array(); $transaction->setNewValue(array()); } $transaction->setOldValue($task->getCCPHIDs()); break; case ManiphestTransactionType::TYPE_PROJECTS: if ($value) { $value = json_decode($value); $phids = $value; foreach ($task->getProjectPHIDs() as $project_phid) { $phids[] = $project_phid; $value[] = $project_phid; } $transaction->setNewValue($value); } else { $phids = array(); $transaction->setNewValue(array()); } $transaction->setOldValue($task->getProjectPHIDs()); break; default: $phids = array(); $transaction->setNewValue($value); break; } $phids[] = $user->getPHID(); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $transactions = array(); $transactions[] = $transaction; $engine = new PhabricatorMarkupEngine(); $engine->addObject($transaction, ManiphestTransaction::MARKUP_FIELD_BODY); $engine->process(); $transaction_view = new ManiphestTransactionListView(); $transaction_view->setTransactions($transactions); $transaction_view->setHandles($handles); $transaction_view->setUser($user); $transaction_view->setMarkupEngine($engine); $transaction_view->setPreview(true); return id(new AphrontAjaxResponse())->setContent($transaction_view->render()); }
public function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) { // NOTE: We'll drop in here on both the "reply to a task" and "create a // new task" workflows! Make sure you test both if you make changes! $task = $this->getMailReceiver(); $is_new_task = !$task->getID(); $user = $this->getActor(); $body = $mail->getCleanTextBody(); $body = trim($body); $xactions = array(); $template = new ManiphestTransaction(); $template->setAuthorPHID($user->getPHID()); if ($is_new_task) { // If this is a new task, create a "User created this task." transaction // and then set the title and description. $xaction = clone $template; $xaction->setTransactionType(ManiphestTransactionType::TYPE_STATUS); $xaction->setNewValue(ManiphestTaskStatus::STATUS_OPEN); $xactions[] = $xaction; $task->setAuthorPHID($user->getPHID()); $task->setTitle(nonempty($mail->getSubject(), 'Untitled Task')); $task->setDescription($body); } else { $lines = explode("\n", trim($body)); $first_line = head($lines); $command = null; $matches = null; if (preg_match('/^!(\\w+)/', $first_line, $matches)) { $lines = array_slice($lines, 1); $body = implode("\n", $lines); $body = trim($body); $command = $matches[1]; } $ttype = ManiphestTransactionType::TYPE_NONE; $new_value = null; switch ($command) { case 'close': $ttype = ManiphestTransactionType::TYPE_STATUS; $new_value = ManiphestTaskStatus::STATUS_CLOSED_RESOLVED; break; case 'claim': $ttype = ManiphestTransactionType::TYPE_OWNER; $new_value = $user->getPHID(); break; case 'unsubscribe': $ttype = ManiphestTransactionType::TYPE_CCS; $ccs = $task->getCCPHIDs(); foreach ($ccs as $k => $phid) { if ($phid == $user->getPHID()) { unset($ccs[$k]); } } $new_value = array_values($ccs); break; } $xaction = clone $template; $xaction->setTransactionType($ttype); $xaction->setNewValue($new_value); $xaction->setComments($body); $xactions[] = $xaction; } // TODO: We should look at CCs on the mail and add them as CCs. $files = $mail->getAttachments(); if ($files) { $file_xaction = clone $template; $file_xaction->setTransactionType(ManiphestTransactionType::TYPE_ATTACH); $phid_type = PhabricatorPHIDConstants::PHID_TYPE_FILE; $new = $task->getAttached(); foreach ($files as $file_phid) { $new[$phid_type][$file_phid] = array(); } $file_xaction->setNewValue($new); $xactions[] = $file_xaction; } $editor = new ManiphestTransactionEditor(); $editor->setParentMessageID($mail->getMessageID()); $editor->applyTransactions($task, $xactions); }
private function applyTaskTransaction(ManiphestTask $task, $attach_type, array $new_phids) { if (!$this->user) { throw new Exception("Call setUser() before editing attachments!"); } $user = $this->user; $editor = new ManiphestTransactionEditor(); $type = ManiphestTransactionType::TYPE_ATTACH; $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID()); $transaction->setTransactionType($type); $new = $task->getAttached(); $new[$attach_type] = array_fill_keys($new_phids, array()); $transaction->setNewValue($new); $editor->applyTransactions($task, array($transaction)); }
public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $id = $request->getURIData('id'); $response_type = $request->getStr('responseType', 'card'); $newStatus = $request->getStr('newStatus'); $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER); $can_edit_status = $this->hasApplicationCapability(ManiphestEditStatusCapability::CAPABILITY); $parent_task = null; $template_id = null; if ($id) { $task = id(new ManiphestTaskQuery())->setViewer($viewer)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->withIDs(array($id))->needSubscriberPHIDs(true)->needProjectPHIDs(true)->executeOne(); if (!$task) { return new Aphront404Response(); } } else { return new Aphront404Response(); } $transaction = new ManiphestTransaction(); $transaction->setTransactionType('status'); if ($newStatus == null) { switch ($task->getStatus()) { case 'open': $newStatus = 'inprogress'; break; case 'inprogress': $newStatus = 'finished'; break; case 'finished': $newStatus = 'delivered'; break; case 'delivered': $newStatus = 'resolved'; break; default: return new Aphront404Response(); } } $transaction->setNewValue($newStatus); $transactions = [$transaction]; if (!$task->getOwnerPHID() && $newStatus == 'inprogress') { // Closing an unassigned task. Assign the user as the owner of // this task. $assign = new ManiphestTransaction(); $assign->setTransactionType(ManiphestTransaction::TYPE_OWNER); $assign->setNewValue($viewer->getPHID()); $transactions[] = $assign; } $editor = id(new ManiphestTransactionEditor())->setActor($viewer)->setContentSourceFromRequest($request)->setContinueOnMissingFields(true); try { $editor->applyTransactions($task, $transactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse())->setCancelURI($task_uri)->setException($ex); } $errors = array(); $e_title = true; $field_list = PhabricatorCustomField::getObjectFields($task, PhabricatorCustomField::ROLE_EDIT); $field_list->setViewer($viewer); $field_list->readFieldsFromStorage($task); $aux_fields = $field_list->getFields(); $v_space = $task->getSpacePHID(); if ($request->isAjax()) { switch ($response_type) { case 'card': $owner = null; if ($task->getOwnerPHID()) { $owner = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs(array($task->getOwnerPHID()))->executeOne(); } $tasks = id(new SprintBoardTaskCard())->setViewer($viewer)->setProject(null)->setTask($task)->setOwner($owner)->setCanEdit(true)->getItem(); $column = id(new PhabricatorProjectColumnQuery())->setViewer($viewer)->withPHIDs(array($request->getStr('columnPHID')))->executeOne(); if (!$column) { return new Aphront404Response(); } // re-load projects for accuracy as they are not re-loaded via // the editor $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs($task->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $task->attachProjectPHIDs($project_phids); $remove_from_board = false; if (!in_array($column->getProjectPHID(), $project_phids)) { $remove_from_board = true; } $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('sortMap' => $sort_map, 'removeFromBoard' => $remove_from_board); break; case 'task': default: $tasks = $this->renderSingleTask($task); $data = array(); break; } return id(new AphrontAjaxResponse())->setContent(array('tasks' => $tasks, 'data' => $data)); } }
private function applyTaskTransaction(ManiphestTask $task, $attach_type, array $new_phids) { $user = $this->getRequest()->getUser(); $editor = new ManiphestTransactionEditor(); $type = ManiphestTransactionType::TYPE_ATTACH; $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID()); $transaction->setTransactionType($type); $new = $task->getAttached(); $new[$attach_type] = array_fill_keys($new_phids, array()); $transaction->setNewValue($new); $editor->applyTransactions($task, array($transaction)); }
/** * Load all the tasks that have been recently closed. */ private function loadRecentlyClosedTasks() { list(, , $window_epoch) = $this->getWindow($this->request); $table = new ManiphestTask(); $xtable = new ManiphestTransaction(); $conn_r = $table->establishConnection('r'); // TODO: Gross. This table is not meant to be queried like this. Build // real stats tables. $rows = queryfx_all($conn_r, 'SELECT t.id FROM %T t JOIN %T x ON x.objectPHID = t.phid WHERE t.status NOT IN (%Ls) AND x.oldValue IN (null, %Ls) AND x.newValue NOT IN (%Ls) AND t.dateModified >= %d AND x.dateCreated >= %d', $table->getTableName(), $xtable->getTableName(), ManiphestTaskStatus::getOpenStatusConstants(), $this->getOpenStatusList(), $this->getOpenStatusList(), $window_epoch, $window_epoch); if (!$rows) { return array(); } $ids = ipull($rows, 'id'); return id(new ManiphestTaskQuery())->setViewer($this->request->getUser())->withIDs($ids)->needProjectPHIDs(true)->execute(); }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $comments = $request->getStr('comments'); $task = id(new ManiphestTaskQuery())->setViewer($user)->withIDs(array($this->id))->executeOne(); if (!$task) { return new Aphront404Response(); } id(new PhabricatorDraft())->setAuthorPHID($user->getPHID())->setDraftKey($task->getPHID())->setDraft($comments)->replaceOrDelete(); $action = $request->getStr('action'); $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID()); $transaction->setTransactionType($action); // This should really be split into a separate transaction, but it should // all come out in the wash once we fully move to modern stuff. $transaction->attachComment(id(new ManiphestTransactionComment())->setContent($comments)); $value = $request->getStr('value'); // grab phids for handles and set transaction values based on action and // value (empty or control-specific format) coming in from the wire switch ($action) { case ManiphestTransaction::TYPE_PRIORITY: $transaction->setOldValue($task->getPriority()); $transaction->setNewValue($value); break; case ManiphestTransaction::TYPE_OWNER: if ($value) { $value = current(json_decode($value)); $phids = array($value); } else { $phids = array(); } $transaction->setNewValue($value); break; case ManiphestTransaction::TYPE_CCS: if ($value) { $value = json_decode($value); } if (!$value) { $value = array(); } $phids = $value; foreach ($task->getCCPHIDs() as $cc_phid) { $phids[] = $cc_phid; $value[] = $cc_phid; } $transaction->setOldValue($task->getCCPHIDs()); $transaction->setNewValue($value); break; case ManiphestTransaction::TYPE_PROJECTS: if ($value) { $value = json_decode($value); } if (!$value) { $value = array(); } $phids = array(); $value = array_fuse($value); foreach ($value as $project_phid) { $phids[] = $project_phid; $value[$project_phid] = array('dst' => $project_phid); } $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $transaction->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $project_type)->setOldValue(array())->setNewValue($value); break; case ManiphestTransaction::TYPE_STATUS: $phids = array(); $transaction->setOldValue($task->getStatus()); $transaction->setNewValue($value); break; default: $phids = array(); $transaction->setNewValue($value); break; } $phids[] = $user->getPHID(); $handles = $this->loadViewerHandles($phids); $transactions = array(); $transactions[] = $transaction; $engine = new PhabricatorMarkupEngine(); $engine->setViewer($user); if ($transaction->hasComment()) { $engine->addObject($transaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } $engine->process(); $transaction->setHandles($handles); $view = id(new PhabricatorApplicationTransactionView())->setUser($user)->setTransactions($transactions)->setIsPreview(true); return id(new AphrontAjaxResponse())->setContent((string) phutil_implode_html('', $view->buildEvents())); }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $task = id(new ManiphestTaskQuery())->setViewer($user)->withIDs(array($request->getStr('taskID')))->executeOne(); if (!$task) { return new Aphront404Response(); } $task_uri = '/' . $task->getMonogram(); $transactions = array(); $action = $request->getStr('action'); // Compute new CCs added by @mentions. Several things can cause CCs to // be added as side effects: mentions, explicit CCs, users who aren't // CC'd interacting with the task, and ownership changes. We build up a // list of all the CCs and then construct a transaction for them at the // end if necessary. $added_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions($user, array($request->getStr('comments'))); $cc_transaction = new ManiphestTransaction(); $cc_transaction->setTransactionType(ManiphestTransaction::TYPE_CCS); $transaction = new ManiphestTransaction(); $transaction->setTransactionType($action); switch ($action) { case ManiphestTransaction::TYPE_STATUS: $transaction->setNewValue($request->getStr('resolution')); break; case ManiphestTransaction::TYPE_OWNER: $assign_to = $request->getArr('assign_to'); $assign_to = reset($assign_to); $transaction->setNewValue($assign_to); break; case ManiphestTransaction::TYPE_PROJECTS: $projects = $request->getArr('projects'); $projects = array_merge($projects, $task->getProjectPHIDs()); $projects = array_filter($projects); $projects = array_unique($projects); // TODO: Bleh. $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $transaction->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $project_type)->setNewValue(array('+' => array_fuse($projects))); break; case ManiphestTransaction::TYPE_CCS: // Accumulate the new explicit CCs into the array that we'll add in // the CC transaction later. $added_ccs = array_merge($added_ccs, $request->getArr('ccs')); // Throw away the primary transaction. $transaction = null; break; case ManiphestTransaction::TYPE_PRIORITY: $transaction->setNewValue($request->getInt('priority')); break; case PhabricatorTransactions::TYPE_COMMENT: // Nuke this, we're going to create it below. $transaction = null; break; default: throw new Exception('unknown action'); } if ($transaction) { $transactions[] = $transaction; } // When you interact with a task, we add you to the CC list so you get // further updates, and possibly assign the task to you if you took an // ownership action (closing it) but it's currently unowned. We also move // previous owners to CC if ownership changes. Detect all these conditions // and create side-effect transactions for them. $implicitly_claimed = false; if ($action == ManiphestTransaction::TYPE_OWNER) { if ($task->getOwnerPHID() == $transaction->getNewValue()) { // If this is actually no-op, don't generate the side effect. } else { // Otherwise, when a task is reassigned, move the previous owner to CC. $added_ccs[] = $task->getOwnerPHID(); } } if ($action == ManiphestTransaction::TYPE_STATUS) { $resolution = $request->getStr('resolution'); if (!$task->getOwnerPHID() && ManiphestTaskStatus::isClosedStatus($resolution)) { // Closing an unassigned task. Assign the user as the owner of // this task. $assign = new ManiphestTransaction(); $assign->setTransactionType(ManiphestTransaction::TYPE_OWNER); $assign->setNewValue($user->getPHID()); $transactions[] = $assign; $implicitly_claimed = true; } } $user_owns_task = false; if ($implicitly_claimed) { $user_owns_task = true; } else { if ($action == ManiphestTransaction::TYPE_OWNER) { if ($transaction->getNewValue() == $user->getPHID()) { $user_owns_task = true; } } else { if ($task->getOwnerPHID() == $user->getPHID()) { $user_owns_task = true; } } } if (!$user_owns_task) { // If we aren't making the user the new task owner and they aren't the // existing task owner, add them to CC unless they're aleady CC'd. if (!in_array($user->getPHID(), $task->getCCPHIDs())) { $added_ccs[] = $user->getPHID(); } } // Evade no-effect detection in the new editor stuff until we can switch // to subscriptions. $added_ccs = array_filter(array_diff($added_ccs, $task->getCCPHIDs())); if ($added_ccs) { // We've added CCs, so include a CC transaction. $all_ccs = array_merge($task->getCCPHIDs(), $added_ccs); $cc_transaction->setNewValue($all_ccs); $transactions[] = $cc_transaction; } $comments = $request->getStr('comments'); if (strlen($comments) || !$transactions) { $transactions[] = id(new ManiphestTransaction())->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)->attachComment(id(new ManiphestTransactionComment())->setContent($comments)); } $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, array('task' => $task, 'new' => false, 'transactions' => $transactions)); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); $task = $event->getValue('task'); $transactions = $event->getValue('transactions'); $editor = id(new ManiphestTransactionEditor())->setActor($user)->setContentSourceFromRequest($request)->setContinueOnMissingFields(true)->setContinueOnNoEffect($request->isContinueRequest()); try { $editor->applyTransactions($task, $transactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse())->setCancelURI($task_uri)->setException($ex); } $draft = id(new PhabricatorDraft())->loadOneWhere('authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if ($draft) { $draft->delete(); } $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, array('task' => $task, 'new' => false, 'transactions' => $transactions)); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); return id(new AphrontRedirectResponse())->setURI($task_uri); }
public function renderTransactionDescription(ManiphestTransaction $transaction, $target) { $label = $this->getLabel(); $old = $transaction->getOldValue(); $new = $transaction->getNewValue(); switch ($this->getFieldType()) { case self::TYPE_BOOL: if ($new) { $desc = "set field '{$label}' true"; } else { $desc = "set field '{$label}' false"; } break; case self::TYPE_SELECT: $old_display = idx($this->getSelectOptions(), $old); $new_display = idx($this->getSelectOptions(), $new); if ($old === null) { $desc = "set field '{$label}' to '{$new_display}'"; } else { $desc = "changed field '{$label}' " . "from '{$old_display}' to '{$new_display}'"; } break; default: if (!strlen($old)) { if (!strlen($new)) { return null; } $desc = "set field '{$label}' to '{$new}'"; } else { $desc = "updated '{$label}' " . "from '{$old}' to '{$new}'"; } break; } if ($target == self::RENDER_TARGET_HTML) { $desc = phutil_escape_html($desc); } return $desc; }
queryfx($table->establishConnection('w'), 'TRUNCATE TABLE %T', DifferentialChangeset::TABLE_CACHE); } echo "Done.\n"; } if ($purge_differential) { echo "Purging Differential comment cache...\n"; $table = new DifferentialComment(); queryfx($table->establishConnection('w'), 'UPDATE %T SET cache = NULL', $table->getTableName()); echo "Purging Differential inline comment cache...\n"; $table = new DifferentialInlineComment(); queryfx($table->establishConnection('w'), 'UPDATE %T SET cache = NULL', $table->getTableName()); echo "Done.\n"; } if ($purge_maniphest) { echo "Purging Maniphest comment cache...\n"; $table = new ManiphestTransaction(); queryfx($table->establishConnection('w'), 'UPDATE %T SET cache = NULL', $table->getTableName()); echo "Done.\n"; } echo "Ok, caches purged.\n"; function usage($message) { echo "Usage Error: {$message}"; echo "\n\n"; echo "Run 'purge_cache.php --help' for detailed help.\n"; exit(1); } function help() { $help = <<<EOHELP **SUMMARY**
/** * Load all the tasks that have been recently closed. */ private function loadRecentlyClosedTasks() { list($ignored, $window_epoch) = $this->getWindow(); $table = new ManiphestTask(); $xtable = new ManiphestTransaction(); $conn_r = $table->establishConnection('r'); // TODO: Gross. This table is not meant to be queried like this. Build // real stats tables. $open_status_list = array(); foreach (ManiphestTaskStatus::getOpenStatusConstants() as $constant) { $open_status_list[] = json_encode((string) $constant); } $rows = queryfx_all($conn_r, 'SELECT t.id FROM %T t JOIN %T x ON x.objectPHID = t.phid WHERE t.status NOT IN (%Ls) AND x.oldValue IN (null, %Ls) AND x.newValue NOT IN (%Ls) AND t.dateModified >= %d AND x.dateCreated >= %d', $table->getTableName(), $xtable->getTableName(), ManiphestTaskStatus::getOpenStatusConstants(), $open_status_list, $open_status_list, $window_epoch, $window_epoch); if (!$rows) { return array(); } $ids = ipull($rows, 'id'); $query = id(new ManiphestTaskQuery())->setViewer($this->getRequest()->getUser())->withIDs($ids); switch ($this->view) { case 'project': $query->needProjectPHIDs(true); break; } return $query->execute(); }
protected function applyRequest(ManiphestTask $task, ConduitAPIRequest $request, $is_new) { $changes = array(); if ($is_new) { $task->setTitle((string) $request->getValue('title')); $task->setDescription((string) $request->getValue('description')); $changes[ManiphestTransactionType::TYPE_STATUS] = ManiphestTaskStatus::STATUS_OPEN; } else { $comments = $request->getValue('comments'); if (!$is_new && $comments !== null) { $changes[ManiphestTransactionType::TYPE_NONE] = null; } $title = $request->getValue('title'); if ($title !== null) { $changes[ManiphestTransactionType::TYPE_TITLE] = $title; } $desc = $request->getValue('description'); if ($desc !== null) { $changes[ManiphestTransactionType::TYPE_DESCRIPTION] = $desc; } $status = $request->getValue('status'); if ($status !== null) { $valid_statuses = ManiphestTaskStatus::getTaskStatusMap(); if (!isset($valid_statuses[$status])) { throw id(new ConduitException('ERR-INVALID-PARAMETER'))->setErrorDescription('Status set to invalid value.'); } $changes[ManiphestTransactionType::TYPE_STATUS] = $status; } } $priority = $request->getValue('priority'); if ($priority !== null) { $valid_priorities = ManiphestTaskPriority::getTaskPriorityMap(); if (!isset($valid_priorities[$priority])) { throw id(new ConduitException('ERR-INVALID-PARAMETER'))->setErrorDescription('Priority set to invalid value.'); } $changes[ManiphestTransactionType::TYPE_PRIORITY] = $priority; } $owner_phid = $request->getValue('ownerPHID'); if ($owner_phid !== null) { $this->validatePHIDList(array($owner_phid), PhabricatorPHIDConstants::PHID_TYPE_USER, 'ownerPHID'); $changes[ManiphestTransactionType::TYPE_OWNER] = $owner_phid; } $ccs = $request->getValue('ccPHIDs'); if ($ccs !== null) { $this->validatePHIDList($ccs, PhabricatorPHIDConstants::PHID_TYPE_USER, 'ccPHIDS'); $changes[ManiphestTransactionType::TYPE_CCS] = $ccs; } $project_phids = $request->getValue('projectPHIDs'); if ($project_phids !== null) { $this->validatePHIDList($project_phids, PhabricatorPHIDConstants::PHID_TYPE_PROJ, 'projectPHIDS'); $changes[ManiphestTransactionType::TYPE_PROJECTS] = $project_phids; } $file_phids = $request->getValue('filePHIDs'); if ($file_phids !== null) { $this->validatePHIDList($file_phids, PhabricatorPHIDConstants::PHID_TYPE_FILE, 'filePHIDS'); $file_map = array_fill_keys($file_phids, true); $attached = $task->getAttached(); $attached[PhabricatorPHIDConstants::PHID_TYPE_FILE] = $file_map; $changes[ManiphestTransactionType::TYPE_ATTACH] = $attached; } $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_CONDUIT, array()); $template = new ManiphestTransaction(); $template->setContentSource($content_source); $template->setAuthorPHID($request->getUser()->getPHID()); $transactions = array(); foreach ($changes as $type => $value) { $transaction = clone $template; $transaction->setTransactionType($type); $transaction->setNewValue($value); if ($type == ManiphestTransactionType::TYPE_NONE) { $transaction->setComments($comments); } $transactions[] = $transaction; } $auxiliary = $request->getValue('auxiliary'); if ($auxiliary) { $task->loadAndAttachAuxiliaryAttributes(); foreach ($auxiliary as $aux_key => $aux_value) { $transaction = clone $template; $transaction->setTransactionType(ManiphestTransactionType::TYPE_AUXILIARY); $transaction->setMetadataValue('aux:key', $aux_key); $transaction->setNewValue($aux_value); $transactions[] = $transaction; } } $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, array('task' => $task, 'new' => $is_new, 'transactions' => $transactions)); $event->setUser($request->getUser()); $event->setConduitRequest($request); PhutilEventEngine::dispatchEvent($event); $task = $event->getValue('task'); $transactions = $event->getValue('transactions'); $editor = new ManiphestTransactionEditor(); $editor->applyTransactions($task, $transactions); $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, array('task' => $task, 'new' => $is_new, 'transactions' => $transactions)); $event->setUser($request->getUser()); $event->setConduitRequest($request); PhutilEventEngine::dispatchEvent($event); }
/** * Load all the tasks that have been recently closed. */ private function loadRecentlyClosedTasks() { list($ignored, $window_epoch) = $this->getWindow(); $table = new ManiphestTask(); $xtable = new ManiphestTransaction(); $conn_r = $table->establishConnection('r'); $tasks = queryfx_all($conn_r, 'SELECT t.* FROM %T t JOIN %T x ON x.taskID = t.id WHERE t.status != 0 AND x.oldValue IN (null, %s, %s) AND x.newValue NOT IN (%s, %s) AND t.dateModified >= %d AND x.dateCreated >= %d', $table->getTableName(), $xtable->getTableName(), json_encode((int) ManiphestTaskStatus::STATUS_OPEN), json_encode((string) ManiphestTaskStatus::STATUS_OPEN), json_encode((int) ManiphestTaskStatus::STATUS_OPEN), json_encode((string) ManiphestTaskStatus::STATUS_OPEN), $window_epoch, $window_epoch); return id(new ManiphestTask())->loadAllFromArray($tasks); }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $task = id(new ManiphestTask())->load($request->getStr('taskID')); if (!$task) { return new Aphront404Response(); } $transactions = array(); $action = $request->getStr('action'); // If we have drag-and-dropped files, attach them first in a separate // transaction. These can come in on any transaction type, which is why we // handle them separately. $files = array(); // Look for drag-and-drop uploads first. $file_phids = $request->getArr('files'); if ($file_phids) { $files = id(new PhabricatorFile())->loadAllWhere('phid in (%Ls)', $file_phids); } // This means "attach a file" even though we store other types of data // as 'attached'. if ($action == ManiphestTransactionType::TYPE_ATTACH) { if (!empty($_FILES['file'])) { $err = idx($_FILES['file'], 'error'); if ($err != UPLOAD_ERR_NO_FILE) { $file = PhabricatorFile::newFromPHPUpload($_FILES['file'], array('authorPHID' => $user->getPHID())); $files[] = $file; } } } // If we had explicit or drag-and-drop files, create a transaction // for those before we deal with whatever else might have happened. $file_transaction = null; if ($files) { $files = mpull($files, 'getPHID', 'getPHID'); $new = $task->getAttached(); foreach ($files as $phid) { if (empty($new[PhabricatorPHIDConstants::PHID_TYPE_FILE])) { $new[PhabricatorPHIDConstants::PHID_TYPE_FILE] = array(); } $new[PhabricatorPHIDConstants::PHID_TYPE_FILE][$phid] = array(); } $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID())->setTransactionType(ManiphestTransactionType::TYPE_ATTACH); $transaction->setNewValue($new); $transactions[] = $transaction; $file_transaction = $transaction; } // Compute new CCs added by @mentions. Several things can cause CCs to // be added as side effects: mentions, explicit CCs, users who aren't // CC'd interacting with the task, and ownership changes. We build up a // list of all the CCs and then construct a transaction for them at the // end if necessary. $added_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions(array($request->getStr('comments'))); $cc_transaction = new ManiphestTransaction(); $cc_transaction->setAuthorPHID($user->getPHID())->setTransactionType(ManiphestTransactionType::TYPE_CCS); $force_cc_transaction = false; $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID())->setComments($request->getStr('comments'))->setTransactionType($action); switch ($action) { case ManiphestTransactionType::TYPE_STATUS: $transaction->setNewValue($request->getStr('resolution')); break; case ManiphestTransactionType::TYPE_OWNER: $assign_to = $request->getArr('assign_to'); $assign_to = reset($assign_to); $transaction->setNewValue($assign_to); break; case ManiphestTransactionType::TYPE_PROJECTS: $projects = $request->getArr('projects'); $projects = array_merge($projects, $task->getProjectPHIDs()); $projects = array_filter($projects); $projects = array_unique($projects); $transaction->setNewValue($projects); break; case ManiphestTransactionType::TYPE_CCS: // Accumulate the new explicit CCs into the array that we'll add in // the CC transaction later. $added_ccs = array_merge($added_ccs, $request->getArr('ccs')); // Transfer any comments over to the CC transaction. $cc_transaction->setComments($transaction->getComments()); // Make sure we include this transaction, even if the user didn't // actually add any CC's, because we'll discard their comment otherwise. $force_cc_transaction = true; // Throw away the primary transaction. $transaction = null; break; case ManiphestTransactionType::TYPE_PRIORITY: $transaction->setNewValue($request->getInt('priority')); break; case ManiphestTransactionType::TYPE_NONE: case ManiphestTransactionType::TYPE_ATTACH: // If we have a file transaction, just get rid of this secondary // transaction and put the comments on it instead. if ($file_transaction) { $file_transaction->setComments($transaction->getComments()); $transaction = null; } break; default: throw new Exception('unknown action'); } if ($transaction) { $transactions[] = $transaction; } // When you interact with a task, we add you to the CC list so you get // further updates, and possibly assign the task to you if you took an // ownership action (closing it) but it's currently unowned. We also move // previous owners to CC if ownership changes. Detect all these conditions // and create side-effect transactions for them. $implicitly_claimed = false; switch ($action) { case ManiphestTransactionType::TYPE_OWNER: if ($task->getOwnerPHID() == $transaction->getNewValue()) { // If this is actually no-op, don't generate the side effect. break; } // Otherwise, when a task is reassigned, move the previous owner to CC. $added_ccs[] = $task->getOwnerPHID(); break; case ManiphestTransactionType::TYPE_STATUS: if (!$task->getOwnerPHID() && $request->getStr('resolution') != ManiphestTaskStatus::STATUS_OPEN) { // Closing an unassigned task. Assign the user as the owner of // this task. $assign = new ManiphestTransaction(); $assign->setAuthorPHID($user->getPHID()); $assign->setTransactionType(ManiphestTransactionType::TYPE_OWNER); $assign->setNewValue($user->getPHID()); $transactions[] = $assign; $implicitly_claimed = true; } break; } $user_owns_task = false; if ($implicitly_claimed) { $user_owns_task = true; } else { if ($action == ManiphestTransactionType::TYPE_OWNER) { if ($transaction->getNewValue() == $user->getPHID()) { $user_owns_task = true; } } else { if ($task->getOwnerPHID() == $user->getPHID()) { $user_owns_task = true; } } } if (!$user_owns_task) { // If we aren't making the user the new task owner and they aren't the // existing task owner, add them to CC. $added_ccs[] = $user->getPHID(); } if ($added_ccs || $force_cc_transaction) { // We've added CCs, so include a CC transaction. It's safe to do this even // if we're just "adding" CCs which already exist, because the // ManiphestTransactionEditor is smart enough to ignore them. $all_ccs = array_merge($task->getCCPHIDs(), $added_ccs); $cc_transaction->setNewValue($all_ccs); $transactions[] = $cc_transaction; } $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_WEB, array('ip' => $request->getRemoteAddr())); foreach ($transactions as $transaction) { $transaction->setContentSource($content_source); } $editor = new ManiphestTransactionEditor(); $editor->applyTransactions($task, $transactions); $draft = id(new PhabricatorDraft())->loadOneWhere('authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if ($draft) { $draft->delete(); } return id(new AphrontRedirectResponse())->setURI('/T' . $task->getID()); }