diff --git a/src/applications/maniphest/auxiliaryfield/ManiphestAuxiliaryFieldSpecification.php b/src/applications/maniphest/auxiliaryfield/ManiphestAuxiliaryFieldSpecification.php index f0df5fb888..37fd89142c 100644 --- a/src/applications/maniphest/auxiliaryfield/ManiphestAuxiliaryFieldSpecification.php +++ b/src/applications/maniphest/auxiliaryfield/ManiphestAuxiliaryFieldSpecification.php @@ -1,261 +1,269 @@ getObject(); } // TODO: Remove; obsolete. public function getUser() { return $this->getViewer(); } public function setLabel($val) { $this->label = $val; return $this; } public function getLabel() { return $this->label; } public function setAuxiliaryKey($val) { $this->auxiliaryKey = $val; return $this; } public function getAuxiliaryKey() { return 'std:maniphest:'.$this->auxiliaryKey; } public function setCaption($val) { $this->caption = $val; return $this; } public function getCaption() { return $this->caption; } public function setValue($val) { $this->value = $val; return $this; } public function getValue() { return $this->value; } public function validate() { return true; } public function isRequired() { return false; } public function setType($val) { $this->type = $val; return $this; } public function getType() { return $this->type; } public function renderControl() { return null; } public function renderForDetailView() { return $this->getValue(); } /** * When the user creates a task, the UI prompts them to "Create another * similar task". This copies some fields (e.g., Owner and CCs) but not other * fields (e.g., description). If this custom field should also be copied, * return true from this method. * * @return bool True to copy the default value from the template task when * creating a new similar task. */ public function shouldCopyWhenCreatingSimilarTask() { return false; } /** * Render a verb to appear in email titles when a transaction involving this * field occurs. Specifically, Maniphest emails are formatted like this: * * [Maniphest] [Verb Here] TNNN: Task title here * ^^^^^^^^^ * * You should optionally return a title-case verb or short phrase like * "Created", "Retitled", "Closed", "Resolved", "Commented On", * "Lowered Priority", etc., which describes the transaction. * * @param ManiphestTransaction The transaction which needs description. * @return string|null A short description of the transaction. */ public function renderTransactionEmailVerb( ManiphestTransaction $transaction) { return null; } /** * Render a short description of the transaction, to appear above comments * in the Maniphest transaction log. The string will be rendered after the * acting user's name. Examples are: * * added a comment * added alincoln to CC * claimed this task * created this task * closed this task out of spite * * You should return a similar string, describing the transaction. * * Note the ##$target## parameter -- Maniphest needs to render transaction * descriptions for different targets, like web and email. This method will * be called with a ##ManiphestAuxiliaryFieldSpecification::RENDER_TARGET_*## * constant describing the intended target. * * @param ManiphestTransaction The transaction which needs description. * @param const Constant describing the rendering target (e.g., html or text). * @return string|null Description of the transaction. */ public function renderTransactionDescription( ManiphestTransaction $transaction, $target) { return 'updated a custom field'; } public function getRequiredHandlePHIDs() { return array(); } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = array_select_keys( $handles, $this->getRequiredHandlePHIDs()); return $this; } public function getHandle($phid) { if (empty($this->handles[$phid])) { throw new Exception( "Field is requesting a handle ('{$phid}') it did not require."); } return $this->handles[$phid]; } public function getMarkupFields() { return array(); } public function setMarkupEngine(PhabricatorMarkupEngine $engine) { $this->markupEngine = $engine; return $this; } public function getMarkupEngine() { return $this->markupEngine; } /* -( PhabricatorMarkupInterface )----------------------------------------- */ public function getMarkupFieldKey($field) { $hash = PhabricatorHash::digestForIndex($this->getMarkupText($field)); return 'maux:'.$this->getAuxiliaryKey().':'.$hash; } public function newMarkupEngine($field) { return PhabricatorMarkupEngine::newManiphestMarkupEngine(); } public function getMarkupText($field) { return $this->getValue(); } public function didMarkupText( $field, $output, PhutilMarkupEngine $engine) { return phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), $output); } public function shouldUseMarkupCache($field) { return true; } /* -( API Compatibility With New Custom Fields )--------------------------- */ public function getFieldKey() { return $this->getAuxiliaryKey(); } public function shouldAppearInEditView() { return true; } public function shouldAppearInPropertyView() { return true; } public function shouldUseStorage() { return true; } + public function renderPropertyViewValue() { + return $this->renderForDetailView(); + } + + public function renderPropertyViewLabel() { + return $this->getLabel(); + } + /* -( Legacy Migration Support )------------------------------------------- */ // TODO: Migrate to common storage and remove this. public static function loadLegacyDataFromStorage( ManiphestTask $task, PhabricatorCustomFieldList $list) { $task->loadAndAttachAuxiliaryAttributes(); foreach ($list->getFields() as $field) { if ($task->getID()) { $key = $field->getAuxiliaryKey(); $field->setValueFromStorage($task->getAuxiliaryAttribute($key)); } } } } diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 79e6e32743..276b0a16c0 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -1,613 +1,608 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $e_title = null; $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); $task = id(new ManiphestTask())->load($this->id); if (!$task) { return new Aphront404Response(); } $workflow = $request->getStr('workflow'); $parent_task = null; if ($workflow && is_numeric($workflow)) { $parent_task = id(new ManiphestTask())->load($workflow); } $transactions = id(new ManiphestTransaction())->loadAllWhere( 'taskID = %d ORDER BY id ASC', $task->getID()); $field_list = PhabricatorCustomField::getObjectFields( $task, PhabricatorCustomField::ROLE_VIEW); foreach ($field_list->getFields() as $field) { $field->setObject($task); $field->setViewer($user); } - ManiphestAuxiliaryFieldSpecification::loadLegacyDataFromStorage( - $task, - $field_list); + $field_list->readFieldsFromStorage($task); $aux_fields = $field_list->getFields(); $e_commit = PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT; $e_dep_on = PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK; $e_dep_by = PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK; $e_rev = PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV; $e_mock = PhabricatorEdgeConfig::TYPE_TASK_HAS_MOCK; $phid = $task->getPHID(); $query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($phid)) ->withEdgeTypes( array( $e_commit, $e_dep_on, $e_dep_by, $e_rev, $e_mock, )); $edges = idx($query->execute(), $phid); $phids = array_fill_keys($query->getDestinationPHIDs(), true); foreach ($transactions as $transaction) { foreach ($transaction->extractPHIDs() as $phid) { $phids[$phid] = true; } } foreach ($task->getCCPHIDs() as $phid) { $phids[$phid] = true; } foreach ($task->getProjectPHIDs() as $phid) { $phids[$phid] = true; } if ($task->getOwnerPHID()) { $phids[$task->getOwnerPHID()] = true; } $phids[$task->getAuthorPHID()] = true; $attached = $task->getAttached(); foreach ($attached as $type => $list) { foreach ($list as $phid => $info) { $phids[$phid] = true; } } if ($parent_task) { $phids[$parent_task->getPHID()] = true; } $phids = array_keys($phids); $phids = array_merge( $phids, array_mergev(mpull($aux_fields, 'getRequiredHandlePHIDs'))); $this->loadHandles($phids); $handles = $this->getLoadedHandles(); foreach ($aux_fields as $aux_field) { $aux_field->setHandles($handles); } $context_bar = null; if ($parent_task) { $context_bar = new AphrontContextBarView(); $context_bar->addButton(phutil_tag( 'a', array( 'href' => '/maniphest/task/create/?parent='.$parent_task->getID(), 'class' => 'green button', ), pht('Create Another Subtask'))); $context_bar->appendChild(hsprintf( 'Created a subtask of %s', $this->getHandle($parent_task->getPHID())->renderLink())); } else if ($workflow == 'create') { $context_bar = new AphrontContextBarView(); $context_bar->addButton(phutil_tag('label', array(), 'Create Another')); $context_bar->addButton(phutil_tag( 'a', array( 'href' => '/maniphest/task/create/?template='.$task->getID(), 'class' => 'green button', ), pht('Similar Task'))); $context_bar->addButton(phutil_tag( 'a', array( 'href' => '/maniphest/task/create/', 'class' => 'green button', ), pht('Empty Task'))); $context_bar->appendChild(pht('New task created.')); } $engine = new PhabricatorMarkupEngine(); $engine->setViewer($user); $engine->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION); foreach ($transactions as $xaction) { if ($xaction->hasComments()) { $engine->addObject($xaction, ManiphestTransaction::MARKUP_FIELD_BODY); } } foreach ($aux_fields as $aux_field) { foreach ($aux_field->getMarkupFields() as $markup_field) { $engine->addObject($aux_field, $markup_field); $aux_field->setMarkupEngine($engine); } } $engine->process(); $transaction_types = ManiphestTransactionType::getTransactionTypeMap(); $resolution_types = ManiphestTaskStatus::getTaskStatusMap(); if ($task->getStatus() == ManiphestTaskStatus::STATUS_OPEN) { $resolution_types = array_select_keys( $resolution_types, array( ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, ManiphestTaskStatus::STATUS_CLOSED_WONTFIX, ManiphestTaskStatus::STATUS_CLOSED_INVALID, ManiphestTaskStatus::STATUS_CLOSED_SPITE, )); } else { $resolution_types = array( ManiphestTaskStatus::STATUS_OPEN => 'Reopened', ); $transaction_types[ManiphestTransactionType::TYPE_STATUS] = 'Reopen Task'; unset($transaction_types[ManiphestTransactionType::TYPE_PRIORITY]); unset($transaction_types[ManiphestTransactionType::TYPE_OWNER]); } $default_claim = array( $user->getPHID() => $user->getUsername().' ('.$user->getRealName().')', ); $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if ($draft) { $draft_text = $draft->getDraft(); } else { $draft_text = null; } $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); if ($is_serious) { // Prevent tasks from being closed "out of spite" in serious business // installs. unset($resolution_types[ManiphestTaskStatus::STATUS_CLOSED_SPITE]); } $comment_form = new AphrontFormView(); $comment_form ->setUser($user) ->setShaded(true) ->setAction('/maniphest/transaction/save/') ->setEncType('multipart/form-data') ->addHiddenInput('taskID', $task->getID()) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Action')) ->setName('action') ->setOptions($transaction_types) ->setID('transaction-action')) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Resolution')) ->setName('resolution') ->setControlID('resolution') ->setControlStyle('display: none') ->setOptions($resolution_types)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Assign To')) ->setName('assign_to') ->setControlID('assign_to') ->setControlStyle('display: none') ->setID('assign-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('CCs')) ->setName('ccs') ->setControlID('ccs') ->setControlStyle('display: none') ->setID('cc-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Priority')) ->setName('priority') ->setOptions($priority_map) ->setControlID('priority') ->setControlStyle('display: none') ->setValue($task->getPriority())) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setControlID('projects') ->setControlStyle('display: none') ->setID('projects-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new AphrontFormFileControl()) ->setLabel(pht('File')) ->setName('file') ->setControlID('file') ->setControlStyle('display: none')) ->appendChild( id(new PhabricatorRemarkupControl()) ->setLabel(pht('Comments')) ->setName('comments') ->setValue($draft_text) ->setID('transaction-comments') ->setUser($user)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue($is_serious ? pht('Submit') : pht('Avast!'))); $control_map = array( ManiphestTransactionType::TYPE_STATUS => 'resolution', ManiphestTransactionType::TYPE_OWNER => 'assign_to', ManiphestTransactionType::TYPE_CCS => 'ccs', ManiphestTransactionType::TYPE_PRIORITY => 'priority', ManiphestTransactionType::TYPE_PROJECTS => 'projects', ManiphestTransactionType::TYPE_ATTACH => 'file', ); $tokenizer_map = array( ManiphestTransactionType::TYPE_PROJECTS => array( 'id' => 'projects-tokenizer', 'src' => '/typeahead/common/projects/', 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'placeholder' => pht('Type a project name...'), ), ManiphestTransactionType::TYPE_OWNER => array( 'id' => 'assign-tokenizer', 'src' => '/typeahead/common/users/', 'value' => $default_claim, 'limit' => 1, 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'placeholder' => pht('Type a user name...'), ), ManiphestTransactionType::TYPE_CCS => array( 'id' => 'cc-tokenizer', 'src' => '/typeahead/common/mailable/', 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'placeholder' => pht('Type a user or mailing list...'), ), ); Javelin::initBehavior('maniphest-transaction-controls', array( 'select' => 'transaction-action', 'controlMap' => $control_map, 'tokenizers' => $tokenizer_map, )); Javelin::initBehavior('maniphest-transaction-preview', array( 'uri' => '/maniphest/transaction/preview/'.$task->getID().'/', 'preview' => 'transaction-preview', 'comments' => 'transaction-comments', 'action' => 'transaction-action', 'map' => $control_map, 'tokenizers' => $tokenizer_map, )); $comment_header = id(new PhabricatorHeaderView()) ->setHeader($is_serious ? pht('Add Comment') : pht('Weigh In')); $preview_panel = hsprintf( '