private function handleDidEditEvent(PhutilEvent $event)
 {
     $id = $event->getValue('id');
     $old_edges = $this->edges[$id];
     $tasks = $this->tasks[$id];
     unset($this->edges[$id]);
     unset($this->tasks[$id]);
     $new_edges = $this->loadAllEdges($event);
     $editor = new ManiphestTransactionEditor();
     foreach ($tasks as $phid => $task) {
         $xactions = array();
         $old = $old_edges[$phid];
         $new = $new_edges[$phid];
         $types = array_keys($old + $new);
         foreach ($types as $type) {
             $old_type = idx($old, $type, array());
             $new_type = idx($new, $type, array());
             if ($old_type === $new_type) {
                 continue;
             }
             $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransactionType::TYPE_EDGE)->setOldValue($old_type)->setNewValue($new_type)->setMetadataValue('edge:type', $type)->setAuthorPHID($event->getUser()->getPHID());
         }
         if ($xactions) {
             $editor->applyTransactions($task, $xactions);
         }
     }
 }
 private function getPriorityTransactions(ManiphestTask $task, $after_phid, $before_phid)
 {
     list($after_task, $before_task) = $this->loadPriorityTasks($after_phid, $before_phid);
     $must_move = false;
     if ($after_task && !$task->isLowerPriorityThan($after_task)) {
         $must_move = true;
     }
     if ($before_task && !$task->isHigherPriorityThan($before_task)) {
         $must_move = true;
     }
     // The move doesn't require a priority change to be valid, so don't
     // change the priority since we are not being forced to.
     if (!$must_move) {
         return array();
     }
     $try = array(array($after_task, true), array($before_task, false));
     $pri = null;
     $sub = null;
     foreach ($try as $spec) {
         list($task, $is_after) = $spec;
         if (!$task) {
             continue;
         }
         list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority($task, $is_after);
     }
     $xactions = array();
     if ($pri !== null) {
         $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)->setNewValue($pri);
         $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)->setNewValue($sub);
     }
     return $xactions;
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     if (!$request->validateCSRF()) {
         return new Aphront403Response();
     }
     $task = id(new ManiphestTaskQuery())->setViewer($viewer)->withIDs(array($request->getInt('task')))->needProjectPHIDs(true)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
     if (!$task) {
         return new Aphront404Response();
     }
     if ($request->getInt('after')) {
         $after_task = id(new ManiphestTaskQuery())->setViewer($viewer)->withIDs(array($request->getInt('after')))->executeOne();
         if (!$after_task) {
             return new Aphront404Response();
         }
         list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority($after_task, $is_after = true);
     } else {
         list($pri, $sub) = ManiphestTransactionEditor::getEdgeSubpriority($request->getInt('priority'), $is_end = false);
     }
     $xactions = array();
     $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)->setNewValue($pri);
     $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)->setNewValue($sub);
     $editor = id(new ManiphestTransactionEditor())->setActor($viewer)->setContinueOnMissingFields(true)->setContinueOnNoEffect(true)->setContentSourceFromRequest($request);
     $editor->applyTransactions($task, $xactions);
     return id(new AphrontAjaxResponse())->setContent(array('tasks' => $this->renderSingleTask($task)));
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $task_ids = $request->getArr('batch');
     $tasks = id(new ManiphestTask())->loadAllWhere('id IN (%Ld)', $task_ids);
     $actions = $request->getStr('actions');
     if ($actions) {
         $actions = json_decode($actions, true);
     }
     if ($request->isFormPost() && is_array($actions)) {
         foreach ($tasks as $task) {
             $xactions = $this->buildTransactions($actions, $task);
             if ($xactions) {
                 $editor = new ManiphestTransactionEditor();
                 $editor->applyTransactions($task, $xactions);
             }
         }
         $task_ids = implode(',', mpull($tasks, 'getID'));
         return id(new AphrontRedirectResponse())->setURI('/maniphest/view/custom/?s=oc&tasks=' . $task_ids);
     }
     $panel = new AphrontPanelView();
     $panel->setHeader('Maniphest Batch Editor');
     $handle_phids = mpull($tasks, 'getOwnerPHID');
     $handles = id(new PhabricatorObjectHandleData($handle_phids))->loadHandles();
     $list = new ManiphestTaskListView();
     $list->setTasks($tasks);
     $list->setUser($user);
     $list->setHandles($handles);
     $template = new AphrontTokenizerTemplateView();
     $template = $template->render();
     require_celerity_resource('maniphest-batch-editor');
     Javelin::initBehavior('maniphest-batch-editor', array('root' => 'maniphest-batch-edit-form', 'tokenizerTemplate' => $template, 'sources' => array('project' => array('src' => '/typeahead/common/projects/', 'placeholder' => 'Type a project name...'), 'owner' => array('src' => '/typeahead/common/searchowner/', 'placeholder' => 'Type a user name...', 'limit' => 1)), 'input' => 'batch-form-actions', 'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(), 'statusMap' => ManiphestTaskStatus::getTaskStatusMap()));
     $form = new AphrontFormView();
     $form->setUser($user);
     $form->setID('maniphest-batch-edit-form');
     foreach ($tasks as $task) {
         $form->appendChild(phutil_render_tag('input', array('type' => 'hidden', 'name' => 'batch[]', 'value' => $task->getID()), null));
     }
     $form->appendChild(phutil_render_tag('input', array('type' => 'hidden', 'name' => 'actions', 'id' => 'batch-form-actions'), null));
     $form->appendChild('<p>These tasks will be edited:</p>');
     $form->appendChild($list);
     $form->appendChild(id(new AphrontFormInsetView())->setTitle('Actions')->setRightButton(javelin_render_tag('a', array('href' => '#', 'class' => 'button green', 'sigil' => 'add-action', 'mustcapture' => true), 'Add Another Action'))->setContent(javelin_render_tag('table', array('sigil' => 'maniphest-batch-actions', 'class' => 'maniphest-batch-actions-table'), '')))->appendChild(id(new AphrontFormSubmitControl())->setValue('Update Tasks')->addCancelButton('/maniphest/', 'Done'));
     $panel->appendChild($form);
     return $this->buildStandardPageResponse($panel, array('title' => 'Batch Editor'));
 }
 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();
     $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));
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
     $column_phid = $request->getStr('columnPHID');
     $object_phid = $request->getStr('objectPHID');
     $after_phid = $request->getStr('afterPHID');
     $before_phid = $request->getStr('beforePHID');
     $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER);
     $project = id(new PhabricatorProjectQuery())->setViewer($viewer)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW))->withIDs(array($id))->executeOne();
     if (!$project) {
         return new Aphront404Response();
     }
     $object = id(new PhabricatorObjectQuery())->setViewer($viewer)->withPHIDs(array($object_phid))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
     if (!$object) {
         return new Aphront404Response();
     }
     $columns = id(new PhabricatorProjectColumnQuery())->setViewer($viewer)->withProjectPHIDs(array($project->getPHID()))->execute();
     $columns = mpull($columns, null, 'getPHID');
     $column = idx($columns, $column_phid);
     if (!$column) {
         // User is trying to drop this object into a nonexistent column, just kick
         // them out.
         return new Aphront404Response();
     }
     $positions = id(new PhabricatorProjectColumnPositionQuery())->setViewer($viewer)->withColumns($columns)->withObjectPHIDs(array($object_phid))->execute();
     $xactions = array();
     if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
         $order_params = array('afterPHID' => $after_phid, 'beforePHID' => $before_phid);
     } else {
         $order_params = array();
     }
     $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN)->setNewValue(array('columnPHIDs' => array($column->getPHID()), 'projectPHID' => $column->getProjectPHID()) + $order_params)->setOldValue(array('columnPHIDs' => mpull($positions, 'getColumnPHID'), 'projectPHID' => $column->getProjectPHID()));
     $task_phids = array();
     if ($after_phid) {
         $task_phids[] = $after_phid;
     }
     if ($before_phid) {
         $task_phids[] = $before_phid;
     }
     if ($task_phids && $order == PhabricatorProjectColumn::ORDER_PRIORITY) {
         $tasks = id(new ManiphestTaskQuery())->setViewer($viewer)->withPHIDs($task_phids)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->execute();
         if (count($tasks) != count($task_phids)) {
             return new Aphront404Response();
         }
         $tasks = mpull($tasks, null, 'getPHID');
         $try = array(array($after_phid, true), array($before_phid, false));
         $pri = null;
         $sub = null;
         foreach ($try as $spec) {
             list($task_phid, $is_after) = $spec;
             $task = idx($tasks, $task_phid);
             if ($task) {
                 list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority($task, $is_after);
                 break;
             }
         }
         if ($pri !== null) {
             $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)->setNewValue($pri);
             $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)->setNewValue($sub);
         }
     }
     $editor = id(new ManiphestTransactionEditor())->setActor($viewer)->setContinueOnMissingFields(true)->setContinueOnNoEffect(true)->setContentSourceFromRequest($request);
     $editor->applyTransactions($object, $xactions);
     $owner = null;
     if ($object->getOwnerPHID()) {
         $owner = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs(array($object->getOwnerPHID()))->executeOne();
     }
     $card = id(new ProjectBoardTaskCard())->setViewer($viewer)->setTask($object)->setOwner($owner)->setCanEdit(true)->getItem();
     return id(new AphrontAjaxResponse())->setContent(array('task' => $card));
 }
 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);
 }
 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);
 }
 public function processReceivedMail()
 {
     // If Phabricator sent the mail, always drop it immediately. This prevents
     // loops where, e.g., the public bug address is also a user email address
     // and creating a bug sends them an email, which loops.
     $is_phabricator_mail = idx($this->headers, 'x-phabricator-sent-this-message');
     if ($is_phabricator_mail) {
         $message = "Ignoring email with 'X-Phabricator-Sent-This-Message' " . "header to avoid loops.";
         return $this->setMessage($message)->save();
     }
     list($to, $receiver_name, $user_id, $hash) = $this->getPhabricatorToInformation();
     if (!$to) {
         $raw_to = idx($this->headers, 'to');
         return $this->setMessage("Unrecognized 'to' format: {$raw_to}")->save();
     }
     $from = idx($this->headers, 'from');
     // TODO -- make this a switch statement / better if / when we add more
     // public create email addresses!
     $create_task = PhabricatorEnv::getEnvConfig('metamta.maniphest.public-create-email');
     if ($create_task && $to == $create_task) {
         $receiver = new ManiphestTask();
         $user = $this->lookupPublicUser();
         if ($user) {
             $this->setAuthorPHID($user->getPHID());
         } else {
             $default_author = PhabricatorEnv::getEnvConfig('metamta.maniphest.default-public-author');
             if ($default_author) {
                 $user = id(new PhabricatorUser())->loadOneWhere('username = %s', $default_author);
                 if ($user) {
                     $receiver->setOriginalEmailSource($from);
                 } else {
                     throw new Exception("Phabricator is misconfigured, the configuration key " . "'metamta.maniphest.default-public-author' is set to user " . "'{$default_author}' but that user does not exist.");
                 }
             } else {
                 // TODO: We should probably bounce these since from the user's
                 // perspective their email vanishes into a black hole.
                 return $this->setMessage("Invalid public user '{$from}'.")->save();
             }
         }
         $receiver->setAuthorPHID($user->getPHID());
         $receiver->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
         $handler->setActor($user);
         $handler->processEmail($this);
         $this->setRelatedPHID($receiver->getPHID());
         $this->setMessage('OK');
         return $this->save();
     }
     if ($user_id == 'public') {
         if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
             return $this->setMessage("Public replies not enabled.")->save();
         }
         $user = $this->lookupPublicUser();
         if (!$user) {
             return $this->setMessage("Invalid public user '{$from}'.")->save();
         }
         $use_user_hash = false;
     } else {
         $user = id(new PhabricatorUser())->load($user_id);
         if (!$user) {
             return $this->setMessage("Invalid private user '{$user_id}'.")->save();
         }
         $use_user_hash = true;
     }
     if ($user->getIsDisabled()) {
         return $this->setMessage("User '{$user_id}' is disabled")->save();
     }
     $this->setAuthorPHID($user->getPHID());
     $receiver = self::loadReceiverObject($receiver_name);
     if (!$receiver) {
         return $this->setMessage("Invalid object '{$receiver_name}'")->save();
     }
     $this->setRelatedPHID($receiver->getPHID());
     if ($use_user_hash) {
         // This is a private reply-to address, check that the user hash is
         // correct.
         $check_phid = $user->getPHID();
     } else {
         // This is a public reply-to address, check that the object hash is
         // correct.
         $check_phid = $receiver->getPHID();
     }
     $expect_hash = self::computeMailHash($receiver->getMailKey(), $check_phid);
     // See note at computeOldMailHash().
     $old_hash = self::computeOldMailHash($receiver->getMailKey(), $check_phid);
     if ($expect_hash != $hash && $old_hash != $hash) {
         return $this->setMessage("Invalid mail hash!")->save();
     }
     if ($receiver instanceof ManiphestTask) {
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
     } else {
         if ($receiver instanceof DifferentialRevision) {
             $handler = DifferentialMail::newReplyHandlerForRevision($receiver);
         } else {
             if ($receiver instanceof PhabricatorRepositoryCommit) {
                 $handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit($receiver);
             }
         }
     }
     $handler->setActor($user);
     $handler->processEmail($this);
     $this->setMessage('OK');
     return $this->save();
 }
 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 applyTransactions(ManiphestTask $task, array $transactions)
 {
     assert_instances_of($transactions, 'ManiphestTransaction');
     $email_cc = $task->getCCPHIDs();
     $email_to = array();
     $email_to[] = $task->getOwnerPHID();
     $pri_changed = $this->isCreate($transactions);
     foreach ($transactions as $key => $transaction) {
         $type = $transaction->getTransactionType();
         $new = $transaction->getNewValue();
         $email_to[] = $transaction->getAuthorPHID();
         $value_is_phid_set = false;
         switch ($type) {
             case ManiphestTransactionType::TYPE_NONE:
                 $old = null;
                 break;
             case ManiphestTransactionType::TYPE_STATUS:
                 $old = $task->getStatus();
                 break;
             case ManiphestTransactionType::TYPE_OWNER:
                 $old = $task->getOwnerPHID();
                 break;
             case ManiphestTransactionType::TYPE_CCS:
                 $old = $task->getCCPHIDs();
                 $value_is_phid_set = true;
                 break;
             case ManiphestTransactionType::TYPE_PRIORITY:
                 $old = $task->getPriority();
                 break;
             case ManiphestTransactionType::TYPE_ATTACH:
                 $old = $task->getAttached();
                 break;
             case ManiphestTransactionType::TYPE_TITLE:
                 $old = $task->getTitle();
                 break;
             case ManiphestTransactionType::TYPE_DESCRIPTION:
                 $old = $task->getDescription();
                 break;
             case ManiphestTransactionType::TYPE_PROJECTS:
                 $old = $task->getProjectPHIDs();
                 $value_is_phid_set = true;
                 break;
             case ManiphestTransactionType::TYPE_AUXILIARY:
                 $aux_key = $transaction->getMetadataValue('aux:key');
                 if (!$aux_key) {
                     throw new Exception("Expected 'aux:key' metadata on TYPE_AUXILIARY transaction.");
                 }
                 $old = $task->getAuxiliaryAttribute($aux_key);
                 break;
             default:
                 throw new Exception('Unknown action type.');
         }
         $old_cmp = $old;
         $new_cmp = $new;
         if ($value_is_phid_set) {
             // Normalize the old and new values if they are PHID sets so we don't
             // get any no-op transactions where the values differ only by keys,
             // order, duplicates, etc.
             if (is_array($old)) {
                 $old = array_filter($old);
                 $old = array_unique($old);
                 sort($old);
                 $old = array_values($old);
                 $old_cmp = $old;
             }
             if (is_array($new)) {
                 $new = array_filter($new);
                 $new = array_unique($new);
                 $transaction->setNewValue($new);
                 $new_cmp = $new;
                 sort($new_cmp);
                 $new_cmp = array_values($new_cmp);
             }
         }
         if ($old !== null && $old_cmp == $new_cmp) {
             if (count($transactions) > 1 && !$transaction->hasComments()) {
                 // If we have at least one other transaction and this one isn't
                 // doing anything and doesn't have any comments, just throw it
                 // away.
                 unset($transactions[$key]);
                 continue;
             } else {
                 $transaction->setOldValue(null);
                 $transaction->setNewValue(null);
                 $transaction->setTransactionType(ManiphestTransactionType::TYPE_NONE);
             }
         } else {
             switch ($type) {
                 case ManiphestTransactionType::TYPE_NONE:
                     break;
                 case ManiphestTransactionType::TYPE_STATUS:
                     $task->setStatus($new);
                     break;
                 case ManiphestTransactionType::TYPE_OWNER:
                     if ($new) {
                         $handles = id(new PhabricatorObjectHandleData(array($new)))->loadHandles();
                         $task->setOwnerOrdering($handles[$new]->getName());
                     } else {
                         $task->setOwnerOrdering(null);
                     }
                     $task->setOwnerPHID($new);
                     break;
                 case ManiphestTransactionType::TYPE_CCS:
                     $task->setCCPHIDs($new);
                     break;
                 case ManiphestTransactionType::TYPE_PRIORITY:
                     $task->setPriority($new);
                     $pri_changed = true;
                     break;
                 case ManiphestTransactionType::TYPE_ATTACH:
                     $task->setAttached($new);
                     break;
                 case ManiphestTransactionType::TYPE_TITLE:
                     $task->setTitle($new);
                     break;
                 case ManiphestTransactionType::TYPE_DESCRIPTION:
                     $task->setDescription($new);
                     break;
                 case ManiphestTransactionType::TYPE_PROJECTS:
                     $task->setProjectPHIDs($new);
                     break;
                 case ManiphestTransactionType::TYPE_AUXILIARY:
                     $aux_key = $transaction->getMetadataValue('aux:key');
                     $task->setAuxiliaryAttribute($aux_key, $new);
                     break;
                 default:
                     throw new Exception('Unknown action type.');
             }
             $transaction->setOldValue($old);
             $transaction->setNewValue($new);
         }
     }
     if ($pri_changed) {
         $subpriority = ManiphestTransactionEditor::getNextSubpriority($task->getPriority(), null);
         $task->setSubpriority($subpriority);
     }
     $task->save();
     foreach ($transactions as $transaction) {
         $transaction->setTaskID($task->getID());
         $transaction->save();
     }
     $email_to[] = $task->getOwnerPHID();
     $email_cc = array_merge($email_cc, $task->getCCPHIDs());
     $this->publishFeedStory($task, $transactions);
     // TODO: Do this offline via timeline
     PhabricatorSearchManiphestIndexer::indexTask($task);
     $this->sendEmail($task, $transactions, $email_to, $email_cc);
 }
 public function processReceivedMail()
 {
     $to = idx($this->headers, 'to');
     $to = $this->getRawEmailAddress($to);
     $from = idx($this->headers, 'from');
     $create_task = PhabricatorEnv::getEnvConfig('metamta.maniphest.public-create-email');
     if ($create_task && $to == $create_task) {
         $user = $this->lookupPublicUser();
         if (!$user) {
             // TODO: We should probably bounce these since from the user's
             // perspective their email vanishes into a black hole.
             return $this->setMessage("Invalid public user '{$from}'.")->save();
         }
         $this->setAuthorPHID($user->getPHID());
         $receiver = new ManiphestTask();
         $receiver->setAuthorPHID($user->getPHID());
         $receiver->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
         $handler->setActor($user);
         $handler->receiveEmail($this);
         $this->setRelatedPHID($receiver->getPHID());
         $this->setMessage('OK');
         return $this->save();
     }
     // We've already stripped this, so look for an object address which has
     // a format like: D291+291+b0a41ca848d66dcc@example.com
     $matches = null;
     $ok = preg_match('/^((?:D|T)\\d+)\\+([\\w]+)\\+([a-f0-9]{16})@/U', $to, $matches);
     if (!$ok) {
         return $this->setMessage("Unrecognized 'to' format: {$to}")->save();
     }
     $receiver_name = $matches[1];
     $user_id = $matches[2];
     $hash = $matches[3];
     if ($user_id == 'public') {
         if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
             return $this->setMessage("Public replies not enabled.")->save();
         }
         $user = $this->lookupPublicUser();
         if (!$user) {
             return $this->setMessage("Invalid public user '{$from}'.")->save();
         }
         $use_user_hash = false;
     } else {
         $user = id(new PhabricatorUser())->load($user_id);
         if (!$user) {
             return $this->setMessage("Invalid private user '{$user_id}'.")->save();
         }
         $use_user_hash = true;
     }
     if ($user->getIsDisabled()) {
         return $this->setMessage("User '{$user_id}' is disabled")->save();
     }
     $this->setAuthorPHID($user->getPHID());
     $receiver = self::loadReceiverObject($receiver_name);
     if (!$receiver) {
         return $this->setMessage("Invalid object '{$receiver_name}'")->save();
     }
     $this->setRelatedPHID($receiver->getPHID());
     if ($use_user_hash) {
         // This is a private reply-to address, check that the user hash is
         // correct.
         $check_phid = $user->getPHID();
     } else {
         // This is a public reply-to address, check that the object hash is
         // correct.
         $check_phid = $receiver->getPHID();
     }
     $expect_hash = self::computeMailHash($receiver->getMailKey(), $check_phid);
     if ($expect_hash != $hash) {
         return $this->setMessage("Invalid mail hash!")->save();
     }
     if ($receiver instanceof ManiphestTask) {
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
     } else {
         if ($receiver instanceof DifferentialRevision) {
             $handler = DifferentialMail::newReplyHandlerForRevision($receiver);
         }
     }
     $handler->setActor($user);
     $handler->receiveEmail($this);
     $this->setMessage('OK');
     return $this->save();
 }
 private function performMerge(ManiphestTask $task, PhabricatorObjectHandle $handle, array $phids)
 {
     $user = $this->getRequest()->getUser();
     $response = id(new AphrontReloadResponse())->setURI($handle->getURI());
     $phids = array_fill_keys($phids, true);
     unset($phids[$task->getPHID()]);
     // Prevent merging a task into itself.
     if (!$phids) {
         return $response;
     }
     $targets = id(new ManiphestTask())->loadAllWhere('phid in (%Ls) ORDER BY id ASC', array_keys($phids));
     if (empty($targets)) {
         return $response;
     }
     $editor = new ManiphestTransactionEditor();
     $task_names = array();
     $merge_into_name = 'T' . $task->getID();
     $cc_vector = array();
     $cc_vector[] = $task->getCCPHIDs();
     foreach ($targets as $target) {
         $cc_vector[] = $target->getCCPHIDs();
         $cc_vector[] = array($target->getAuthorPHID(), $target->getOwnerPHID());
         $close_task = id(new ManiphestTransaction())->setAuthorPHID($user->getPHID())->setTransactionType(ManiphestTransactionType::TYPE_STATUS)->setNewValue(ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE)->setComments("✘ Merged into {$merge_into_name}.");
         $editor->applyTransactions($target, array($close_task));
         $task_names[] = 'T' . $target->getID();
     }
     $all_ccs = array_mergev($cc_vector);
     $all_ccs = array_filter($all_ccs);
     $all_ccs = array_unique($all_ccs);
     $task_names = implode(', ', $task_names);
     $add_ccs = id(new ManiphestTransaction())->setAuthorPHID($user->getPHID())->setTransactionType(ManiphestTransactionType::TYPE_CCS)->setNewValue($all_ccs)->setComments("◀ Merged tasks: {$task_names}.");
     $editor->applyTransactions($task, array($add_ccs));
     return $response;
 }
 public function processReceivedMail()
 {
     $to = idx($this->headers, 'to');
     $to = $this->getRawEmailAddress($to);
     $from = idx($this->headers, 'from');
     $create_task = PhabricatorEnv::getEnvConfig('metamta.maniphest.public-create-email');
     if ($create_task && $to == $create_task) {
         $receiver = new ManiphestTask();
         $user = $this->lookupPublicUser();
         if ($user) {
             $this->setAuthorPHID($user->getPHID());
         } else {
             $default_author = PhabricatorEnv::getEnvConfig('metamta.maniphest.default-public-author');
             if ($default_author) {
                 $user = id(new PhabricatorUser())->loadOneWhere('username = %s', $default_author);
                 if ($user) {
                     $receiver->setOriginalEmailSource($from);
                 } else {
                     throw new Exception("Phabricator is misconfigured, the configuration key " . "'metamta.maniphest.default-public-author' is set to user " . "'{$default_author}' but that user does not exist.");
                 }
             } else {
                 // TODO: We should probably bounce these since from the user's
                 // perspective their email vanishes into a black hole.
                 return $this->setMessage("Invalid public user '{$from}'.")->save();
             }
         }
         $receiver->setAuthorPHID($user->getPHID());
         $receiver->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
         $handler->setActor($user);
         $handler->receiveEmail($this);
         $this->setRelatedPHID($receiver->getPHID());
         $this->setMessage('OK');
         return $this->save();
     }
     // We've already stripped this, so look for an object address which has
     // a format like: D291+291+b0a41ca848d66dcc@example.com
     $matches = null;
     $single_handle_prefix = PhabricatorEnv::getEnvConfig('metamta.single-reply-handler-prefix');
     $prefixPattern = $single_handle_prefix ? preg_quote($single_handle_prefix, '/') . '\\+' : '';
     $pattern = "/^{$prefixPattern}((?:D|T)\\d+)\\+([\\w]+)\\+([a-f0-9]{16})@/U";
     $ok = preg_match($pattern, $to, $matches);
     if (!$ok) {
         return $this->setMessage("Unrecognized 'to' format: {$to}")->save();
     }
     $receiver_name = $matches[1];
     $user_id = $matches[2];
     $hash = $matches[3];
     if ($user_id == 'public') {
         if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
             return $this->setMessage("Public replies not enabled.")->save();
         }
         $user = $this->lookupPublicUser();
         if (!$user) {
             return $this->setMessage("Invalid public user '{$from}'.")->save();
         }
         $use_user_hash = false;
     } else {
         $user = id(new PhabricatorUser())->load($user_id);
         if (!$user) {
             return $this->setMessage("Invalid private user '{$user_id}'.")->save();
         }
         $use_user_hash = true;
     }
     if ($user->getIsDisabled()) {
         return $this->setMessage("User '{$user_id}' is disabled")->save();
     }
     $this->setAuthorPHID($user->getPHID());
     $receiver = self::loadReceiverObject($receiver_name);
     if (!$receiver) {
         return $this->setMessage("Invalid object '{$receiver_name}'")->save();
     }
     $this->setRelatedPHID($receiver->getPHID());
     if ($use_user_hash) {
         // This is a private reply-to address, check that the user hash is
         // correct.
         $check_phid = $user->getPHID();
     } else {
         // This is a public reply-to address, check that the object hash is
         // correct.
         $check_phid = $receiver->getPHID();
     }
     $expect_hash = self::computeMailHash($receiver->getMailKey(), $check_phid);
     // See note at computeOldMailHash().
     $old_hash = self::computeOldMailHash($receiver->getMailKey(), $check_phid);
     if ($expect_hash != $hash && $old_hash != $hash) {
         return $this->setMessage("Invalid mail hash!")->save();
     }
     if ($receiver instanceof ManiphestTask) {
         $editor = new ManiphestTransactionEditor();
         $handler = $editor->buildReplyHandler($receiver);
     } else {
         if ($receiver instanceof DifferentialRevision) {
             $handler = DifferentialMail::newReplyHandlerForRevision($receiver);
         }
     }
     $handler->setActor($user);
     $handler->receiveEmail($this);
     $this->setMessage('OK');
     return $this->save();
 }
 private function movePriority(PhabricatorUser $viewer, $src, $target_priority, $is_end)
 {
     list($pri, $sub) = ManiphestTransactionEditor::getEdgeSubpriority($target_priority, $is_end);
     $xactions = array();
     $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)->setNewValue($pri);
     $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)->setNewValue($sub);
     return $this->applyTaskTransactions($viewer, $src, $xactions);
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getViewer();
     $id = $request->getURIData('id');
     $column_phid = $request->getStr('columnPHID');
     $object_phid = $request->getStr('objectPHID');
     $after_phid = $request->getStr('afterPHID');
     $before_phid = $request->getStr('beforePHID');
     $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER);
     $project = id(new PhabricatorProjectQuery())->setViewer($viewer)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW))->withIDs(array($id))->executeOne();
     if (!$project) {
         return new Aphront404Response();
     }
     $is_sprint = $this->isSprint($project);
     $board_phid = $project->getPHID();
     $object = id(new ManiphestTaskQuery())->setViewer($viewer)->withPHIDs(array($object_phid))->needProjectPHIDs(true)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
     if (!$object) {
         return new Aphront404Response();
     }
     $columns = id(new PhabricatorProjectColumnQuery())->setViewer($viewer)->withProjectPHIDs(array($project->getPHID()))->execute();
     $columns = mpull($columns, null, 'getPHID');
     $column = idx($columns, $column_phid);
     if (!$column) {
         // User is trying to drop this object into a nonexistent column, just kick
         // them out.
         return new Aphront404Response();
     }
     $engine = id(new PhabricatorBoardLayoutEngine())->setViewer($viewer)->setBoardPHIDs(array($board_phid))->setObjectPHIDs(array($object_phid))->executeLayout();
     $columns = $engine->getObjectColumns($board_phid, $object_phid);
     $old_column_phids = mpull($columns, 'getPHID');
     $xactions = array();
     if ($order == PhabricatorProjectColumn::ORDER_NATURAL) {
         $order_params = array('afterPHID' => $after_phid, 'beforePHID' => $before_phid);
     } else {
         $order_params = array();
     }
     $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN)->setNewValue(array('columnPHIDs' => array($column->getPHID()), 'projectPHID' => $column->getProjectPHID()) + $order_params)->setOldValue(array('columnPHIDs' => $old_column_phids, 'projectPHID' => $column->getProjectPHID()));
     $task_phids = array();
     if ($after_phid) {
         $task_phids[] = $after_phid;
     }
     if ($before_phid) {
         $task_phids[] = $before_phid;
     }
     if ($task_phids && $order == PhabricatorProjectColumn::ORDER_PRIORITY) {
         $tasks = id(new ManiphestTaskQuery())->setViewer($viewer)->withPHIDs($task_phids)->needProjectPHIDs(true)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->execute();
         if (count($tasks) != count($task_phids)) {
             return new Aphront404Response();
         }
         $tasks = mpull($tasks, null, 'getPHID');
         $try = array(array($after_phid, true), array($before_phid, false));
         $pri = null;
         $sub = null;
         foreach ($try as $spec) {
             list($task_phid, $is_after) = $spec;
             $task = idx($tasks, $task_phid);
             if ($task) {
                 list($pri, $sub) = ManiphestTransactionEditor::getAdjacentSubpriority($task, $is_after);
                 break;
             }
         }
         if ($pri !== null) {
             $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_PRIORITY)->setNewValue($pri);
             $xactions[] = id(new ManiphestTransaction())->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY)->setNewValue($sub);
         }
     }
     $proxy = $column->getProxy();
     if ($proxy) {
         // We're moving the task into a subproject or milestone column, so add
         // the subproject or milestone.
         $add_projects = array($proxy->getPHID());
     } else {
         if ($project->getHasSubprojects() || $project->getHasMilestones()) {
             // We're moving the task into the "Backlog" column on the parent project,
             // so add the parent explicitly. This gets rid of any subproject or
             // milestone tags.
             $add_projects = array($project->getPHID());
         } else {
             $add_projects = array();
         }
     }
     if ($add_projects) {
         $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
         $xactions[] = id(new ManiphestTransaction())->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $project_type)->setNewValue(array('+' => array_fuse($add_projects)));
     }
     $editor = id(new ManiphestTransactionEditor())->setActor($viewer)->setContinueOnMissingFields(true)->setContinueOnNoEffect(true)->setContentSourceFromRequest($request);
     $editor->applyTransactions($object, $xactions);
     $owner = null;
     if ($object->getOwnerPHID()) {
         $owner = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs(array($object->getOwnerPHID()))->executeOne();
     }
     // Reload the object so it reflects edits which have been applied.
     $object = id(new ManiphestTaskQuery())->setViewer($viewer)->withPHIDs(array($object_phid))->needProjectPHIDs(true)->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->executeOne();
     $except_phids = array($board_phid);
     if ($project->getHasSubprojects() || $project->getHasMilestones()) {
         $descendants = id(new PhabricatorProjectQuery())->setViewer($viewer)->withAncestorProjectPHIDs($except_phids)->execute();
         foreach ($descendants as $descendant) {
             $except_phids[] = $descendant->getPHID();
         }
     }
     $except_phids = array_fuse($except_phids);
     $handle_phids = array_fuse($object->getProjectPHIDs());
     $handle_phids = array_diff_key($handle_phids, $except_phids);
     $project_handles = $viewer->loadHandles($handle_phids);
     $project_handles = iterator_to_array($project_handles);
     if ($is_sprint == true) {
         $card = id(new SprintBoardTaskCard())->setProject($project)->setProjectHandles($project_handles)->setViewer($viewer)->setTask($object)->setOwner($owner)->setCanEdit(true)->getItem();
     } else {
         $card = id(new ProjectBoardTaskCard())->setViewer($viewer)->setTask($object)->setProjectHandles($project_handles)->setOwner($owner)->setCanEdit(true)->getItem();
     }
     $card->addClass('phui-workcard');
     return id(new AphrontAjaxResponse())->setContent(array('task' => $card));
 }
 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());
 }