diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -81,6 +81,7 @@ ->setLabel(pht('Status')) ->setDescription(pht('Status of the task.')) ->setTransactionType(ManiphestTransaction::TYPE_STATUS) + ->setIsCopyable(true) ->setValue($object->getStatus()) ->setOptions($status_map) ->setCommentActionLabel(pht('Change Status')) @@ -91,6 +92,7 @@ ->setLabel(pht('Assigned To')) ->setDescription(pht('User who is responsible for the task.')) ->setTransactionType(ManiphestTransaction::TYPE_OWNER) + ->setIsCopyable(true) ->setSingleValue($object->getOwnerPHID()) ->setCommentActionLabel(pht('Assign / Claim')) ->setCommentActionDefaultValue($owner_value), @@ -99,6 +101,7 @@ ->setLabel(pht('Priority')) ->setDescription(pht('Priority of the task.')) ->setTransactionType(ManiphestTransaction::TYPE_PRIORITY) + ->setIsCopyable(true) ->setValue($object->getPriority()) ->setOptions($priority_map) ->setCommentActionLabel($priority_label), diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php --- a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php @@ -58,6 +58,7 @@ ->setDescription(pht('Users and projects which own the package.')) ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_OWNERS) ->setDatasource(new PhabricatorProjectOrUserDatasource()) + ->setIsCopyable(true) ->setValue($object->getOwnerPHIDs()), id(new PhabricatorSelectEditField()) ->setKey('status') @@ -74,6 +75,7 @@ 'Automatically trigger audits for commits affecting files in '. 'this package.')) ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_AUDITING) + ->setIsCopyable(true) ->setValue($object->getAuditingEnabled()) ->setOptions( array( diff --git a/src/applications/paste/editor/PhabricatorPasteEditEngine.php b/src/applications/paste/editor/PhabricatorPasteEditEngine.php --- a/src/applications/paste/editor/PhabricatorPasteEditEngine.php +++ b/src/applications/paste/editor/PhabricatorPasteEditEngine.php @@ -73,6 +73,7 @@ 'title.')) ->setAliases(array('lang')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) + ->setIsCopyable(true) ->setValue($object->getLanguage()) ->setOptions($langs), id(new PhabricatorSelectEditField()) diff --git a/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php b/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php --- a/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php +++ b/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php @@ -83,6 +83,7 @@ ->setLabel($label) ->setDescription($description) ->setAliases($aliases) + ->setIsCopyable(true) ->setCapability($capability) ->setPolicies($policies) ->setTransactionType($type) @@ -100,6 +101,7 @@ ->setEditTypeKey('space') ->setDescription( pht('Shifts the object in the Spaces application.')) + ->setIsCopyable(true) ->setIsReorderable(false) ->setAliases(array('space', 'policy.space')) ->setTransactionType($type_space) diff --git a/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php b/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php --- a/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php +++ b/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php @@ -48,6 +48,7 @@ ->setEditTypeKey('projects') ->setDescription(pht('Add or remove associated projects.')) ->setAliases(array('project', 'projects')) + ->setIsCopyable(true) ->setUseEdgeTransactions(true) ->setEdgeTransactionDescriptions( pht('Add projects.'), diff --git a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php --- a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php +++ b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php @@ -45,6 +45,7 @@ ->setEditTypeKey('subscribers') ->setDescription(pht('Manage subscribers.')) ->setAliases(array('subscriber', 'subscribers')) + ->setIsCopyable(true) ->setUseEdgeTransactions(true) ->setEdgeTransactionDescriptions( pht('Add subscribers.'), diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -95,7 +95,7 @@ } $config = $this->getEditEngineConfiguration(); - $fields = $config->applyConfigurationToFields($this, $fields); + $fields = $config->applyConfigurationToFields($this, $object, $fields); foreach ($fields as $field) { $field @@ -443,12 +443,16 @@ * to make Conduit a little easier to use. * * @param wild ID, PHID, or monogram. + * @param list List of required capability constants, or omit for + * defaults. * @return object Corresponding editable object. * @task load */ - private function newObjectFromIdentifier($identifier) { + private function newObjectFromIdentifier( + $identifier, + array $capabilities = array()) { if (is_int($identifier) || ctype_digit($identifier)) { - $object = $this->newObjectFromID($identifier); + $object = $this->newObjectFromID($identifier, $capabilities); if (!$object) { throw new Exception( @@ -462,7 +466,7 @@ $type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN; if (phid_get_type($identifier) != $type_unknown) { - $object = $this->newObjectFromPHID($identifier); + $object = $this->newObjectFromPHID($identifier, $capabilities); if (!$object) { throw new Exception( @@ -503,7 +507,7 @@ // sure it's really valid, goes through standard policy check logic, and // picks up any `need...()` clauses we want it to load with. - $object = $this->newObjectFromPHID($target->getPHID()); + $object = $this->newObjectFromPHID($target->getPHID(), $capabilities); if (!$object) { throw new Exception( pht( @@ -536,14 +540,16 @@ * Load an object by PHID. * * @param phid Object PHID. + * @param list List of required capability constants, or omit for + * defaults. * @return object|null Object, or null if no such object exists. * @task load */ - private function newObjectFromPHID($phid) { + private function newObjectFromPHID($phid, array $capabilities = array()) { $query = $this->newObjectQuery() ->withPHIDs(array($phid)); - return $this->newObjectFromQuery($query); + return $this->newObjectFromQuery($query, $capabilities); } @@ -629,6 +635,9 @@ ); $use_default = true; break; + case 'parameters': + $use_default = true; + break; default: break; } @@ -769,11 +778,43 @@ } } else { if ($this->getIsCreate()) { + $template = $request->getStr('template'); + + if (strlen($template)) { + $template_object = $this->newObjectFromIdentifier( + $template, + array( + PhabricatorPolicyCapability::CAN_VIEW, + )); + if (!$template_object) { + return new Aphront404Response(); + } + } else { + $template_object = null; + } + + if ($template_object) { + $copy_fields = $this->buildEditFields($template_object); + $copy_fields = mpull($copy_fields, null, 'getKey'); + foreach ($copy_fields as $copy_key => $copy_field) { + if (!$copy_field->getIsCopyable()) { + unset($copy_fields[$copy_key]); + } + } + } else { + $copy_fields = array(); + } + foreach ($fields as $field) { if ($field->getIsLocked() || $field->getIsHidden()) { continue; } + $field_key = $field->getKey(); + if (isset($copy_fields[$field_key])) { + $field->readValueFromField($copy_fields[$field_key]); + } + $field->readValueFromRequest($request); } } diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -28,6 +28,7 @@ private $isReorderable = true; private $isDefaultable = true; private $isLockable = true; + private $isCopyable = false; public function setKey($key) { $this->key = $key; @@ -146,6 +147,15 @@ return $this->isHidden; } + public function setIsCopyable($is_copyable) { + $this->isCopyable = $is_copyable; + return $this; + } + + public function getIsCopyable() { + return $this->isCopyable; + } + public function setIsSubmittedForm($is_submitted) { $this->isSubmittedForm = $is_submitted; return $this; @@ -366,6 +376,15 @@ return $this->getHTTPParameterValue($request, $key); } + public function readValueFromField(PhabricatorEditField $other) { + $this->value = $this->getValueFromField($other); + return $this; + } + + protected function getValueFromField(PhabricatorEditField $other) { + return $other->getValue(); + } + /** * Read and return the value the object had when the user first loaded the diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php --- a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php @@ -89,12 +89,15 @@ public function applyConfigurationToFields( PhabricatorEditEngine $engine, + $object, array $fields) { $fields = mpull($fields, null, 'getKey'); + $is_new = !$object->getID(); + $values = $this->getProperty('defaults', array()); foreach ($fields as $key => $field) { - if ($engine->getIsCreate()) { + if ($is_new) { if (array_key_exists($key, $values)) { $field->readDefaultValueFromConfiguration($values[$key]); } diff --git a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php --- a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php +++ b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php @@ -176,6 +176,59 @@ 'wide', )); + $template_text = pht(<<setIconFont('fa-check-circle green'); + $no = id(new PHUIIconView())->setIconFont('fa-times grey'); + + $rows = array(); + foreach ($fields as $field) { + $rows[] = array( + $field->getLabel(), + $field->getIsCopyable() ? $yes : $no, + ); + } + + $template_table = id(new AphrontTableView($rows)) + ->setNoDataString( + pht('None of the fields on this object support templating.')) + ->setHeaders( + array( + pht('Field'), + pht('Will Copy'), + )) + ->setColumnClasses( + array( + 'pri', + 'wide', + )); + $select_text = pht(<<renderInstructions($aliases_text), $alias_table, + $this->renderInstructions($template_text), + $template_table, $this->renderInstructions($select_text), $select_table, $this->renderInstructions($types_text), diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner --- a/src/docs/user/configuration/custom_fields.diviner +++ b/src/docs/user/configuration/custom_fields.diviner @@ -119,6 +119,8 @@ above the control when rendered on the edit view. - **placeholder**: A placeholder text that appears on text boxes. Only supported in text, int and remarkup fields (optional). + - **copy**: If true, this field's value will be copied when an object is + created using another object as a template. The `strings` value supports different strings per control type. They are: @@ -128,15 +130,6 @@ - **search.default** Text for the search interface, defaults to "(Any)". - **search.require** Text for the search interface, defaults to "Require". -Some applications have specific options which only work in that application. - -In **Maniphest**: - - - **copy**: When a user creates a task, the UI gives them an option to - "Create Another Similar Task". Some fields from the original task are copied - into the new task, while others are not; by default, fields are not copied. - If you want this field to be copied, specify `true` for the `copy` property. - Internally, Phabricator implements some additional custom field types and options. These are not intended for general use and are subject to abrupt change, but are documented here for completeness: diff --git a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditEngineExtension.php b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditEngineExtension.php --- a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditEngineExtension.php +++ b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditEngineExtension.php @@ -35,7 +35,7 @@ $field_list->setViewer($viewer); - if (!$engine->getIsCreate()) { + if ($object->getID()) { $field_list->readFieldsFromStorage($object); } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php @@ -15,6 +15,7 @@ private $fieldError; private $required; private $default; + private $isCopyable; abstract public function getFieldType(); @@ -115,6 +116,9 @@ case 'default': $this->setFieldValue($value); break; + case 'copy': + $this->setIsCopyable($value); + break; case 'type': // We set this earlier on. break; @@ -185,6 +189,15 @@ return idx($this->strings, $key, $default); } + public function setIsCopyable($is_copyable) { + $this->isCopyable = $is_copyable; + return $this; + } + + public function getIsCopyable() { + return $this->isCopyable; + } + public function shouldUseStorage() { try { $object = $this->newStorageObject(); @@ -430,7 +443,8 @@ $short = 'custom.'.$this->getRawStandardFieldKey(); return parent::newStandardEditField() - ->setEditTypeKey($short); + ->setEditTypeKey($short) + ->setIsCopyable($this->getIsCopyable()); } public function shouldAppearInConduitTransactions() {