Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
index dff4d1b62f..8c7fafdc2a 100644
--- a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
@@ -1,198 +1,202 @@
<?php
abstract class PhabricatorEditEngineAPIMethod
extends ConduitAPIMethod {
abstract public function newEditEngine();
public function getApplication() {
$engine = $this->newEditEngine();
$class = $engine->getEngineApplicationClass();
return PhabricatorApplication::getByClass($class);
}
public function getMethodStatus() {
return self::METHOD_STATUS_UNSTABLE;
}
public function getMethodStatusDescription() {
return pht('ApplicationEditor methods are highly unstable.');
}
final protected function defineParamTypes() {
return array(
'transactions' => 'list<map<string, wild>>',
'objectIdentifier' => 'optional id|phid|string',
);
}
final protected function defineReturnType() {
return 'map<string, wild>';
}
final protected function execute(ConduitAPIRequest $request) {
$engine = $this->newEditEngine()
->setViewer($request->getUser());
return $engine->buildConduitResponse($request);
}
final public function getMethodDescription() {
// TODO: We don't currently have a real viewer in this method.
$viewer = PhabricatorUser::getOmnipotentUser();
$engine = $this->newEditEngine()
->setViewer($viewer);
$types = $engine->getConduitEditTypes();
$out = array();
$out[] = pht(<<<EOTEXT
This is a standard **ApplicationEditor** method which allows you to create and
modify objects by applying transactions.
Each transaction applies one change to the object. For example, to create an
object with a specific title or change the title of an existing object you might
start by building a transaction like this:
```lang=json, name=Example Single Transaction
{
"type": "title",
"value": "New Object Title"
}
```
By passing a list of transactions in the `transactions` parameter, you can
apply a sequence of edits. For example, you'll often pass a value like this to
create an object with several field values or apply changes to multiple fields:
```lang=json, name=Example Transaction List
[
{
"type": "title",
"value": "New Object Title"
},
{
"type": "body",
"value": "New body text for the object."
},
{
"type": "projects.add",
"value": ["PHID-PROJ-1111", "PHID-PROJ-2222"]
}
]
```
Exactly which types of edits are available depends on the object you're editing.
Creating Objects
----------------
To create an object, pass a list of `transactions` but leave `objectIdentifier`
empty. This will create a new object with the initial field values you
specify.
Editing Objects
---------------
To edit an object, pass a list of `transactions` and specify an object to
apply them to with `objectIdentifier`. This will apply the changes to the
object.
You may pass an ID (like `123`), PHID (like `PHID-WXYZ-abcdef...`), or
monogram (like `T123`, for objects which have monograms).
Return Type
-----------
WARNING: The structure of the return value from these methods is likely to
change as ApplicationEditor evolves.
Return values look something like this for now:
```lang=json, name=Example Return Value
{
"object": {
"phid": "PHID-XXXX-1111"
},
"transactions": [
{
"phid": "PHID-YYYY-1111",
},
{
"phid": "PHID-YYYY-2222",
}
]
}
```
The `object` key contains information about the object which was created or
edited.
The `transactions` key contains information about the transactions which were
actually applied. For many reasons, the transactions which actually apply may
be greater or fewer in number than the transactions you provided, or may differ
in their nature in other ways.
Edit Types
==========
This API method supports these edit types:
EOTEXT
);
$key = pht('Key');
$summary = pht('Summary');
$description = pht('Description');
$head_type = pht('Type');
$table = array();
$table[] = "| {$key} | {$summary} |";
$table[] = '|--------|----------------|';
foreach ($types as $type) {
$edit_type = $type->getEditType();
$edit_summary = $type->getSummary();
$table[] = "| `{$edit_type}` | {$edit_summary} |";
}
$out[] = implode("\n", $table);
foreach ($types as $type) {
$section = array();
$section[] = pht('Edit Type: %s', $type->getEditType());
$section[] = '---------';
$section[] = null;
$section[] = $type->getDescription();
$section[] = null;
$section[] = pht(
'This edit generates transactions of type `%s` internally.',
$type->getTransactionType());
$section[] = null;
$type_description = pht(
'Use `%s` to select this edit type.',
$type->getEditType());
$value_type = $type->getValueType();
+ if (!strlen($value_type)) {
+ $value_type = '?';
+ }
+
$value_description = $type->getValueDescription();
$table = array();
$table[] = "| {$key} | {$head_type} | {$description} |";
$table[] = '|--------|--------------|----------------|';
$table[] = "| `type` | `const` | {$type_description} |";
$table[] = "| `value` | `{$value_type}` | {$value_description} |";
$section[] = implode("\n", $table);
$out[] = implode("\n", $section);
}
$out = implode("\n\n", $out);
return $out;
}
}
diff --git a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php
index 3aba613e87..e585853a13 100644
--- a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php
+++ b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php
@@ -1,100 +1,112 @@
<?php
final class PhabricatorCustomFieldEditField
extends PhabricatorEditField {
private $customField;
private $httpParameterType;
public function setCustomField(PhabricatorCustomField $custom_field) {
$this->customField = $custom_field;
return $this;
}
public function getCustomField() {
return $this->customField;
}
public function setCustomFieldHTTPParameterType(
AphrontHTTPParameterType $type) {
$this->httpParameterType = $type;
return $this;
}
public function getCustomFieldHTTPParameterType() {
return $this->httpParameterType;
}
protected function buildControl() {
$field = $this->getCustomField();
$clone = clone $field;
$value = $this->getValue();
$clone->setValueFromApplicationTransactions($value);
return $clone->renderEditControl(array());
}
protected function newEditType() {
- return id(new PhabricatorCustomFieldEditType())
+ $type = id(new PhabricatorCustomFieldEditType())
->setCustomField($this->getCustomField());
+
+ $http_type = $this->getHTTPParameterType();
+ if ($http_type) {
+ $type->setValueType($http_type->getTypeName());
+ }
+
+ return $type;
}
public function getValueForTransaction() {
$value = $this->getValue();
$field = $this->getCustomField();
// Avoid changing the value of the field itself, since later calls would
// incorrectly reflect the new value.
$clone = clone $field;
$clone->setValueFromApplicationTransactions($value);
return $clone->getNewValueForApplicationTransactions();
}
protected function getValueExistsInSubmit(AphrontRequest $request, $key) {
return true;
}
protected function getValueFromSubmit(AphrontRequest $request, $key) {
$field = $this->getCustomField();
$clone = clone $field;
$clone->readValueFromRequest($request);
return $clone->getNewValueForApplicationTransactions();
}
public function getConduitEditTypes() {
- // TODO: For now, don't support custom fields over Conduit.
- return array();
+ $field = $this->getCustomField();
+
+ if (!$field->shouldAppearInConduitTransactions()) {
+ return array();
+ }
+
+ return parent::getConduitEditTypes();
}
protected function newHTTPParameterType() {
$type = $this->getCustomFieldHTTPParameterType();
if ($type) {
return clone $type;
}
return null;
}
public function getAllReadValueFromRequestKeys() {
$keys = array();
// NOTE: This piece of complexity is so we can expose a reasonable key in
// the UI ("custom.x") instead of a crufty internal key ("std:app:x").
// Perhaps we can simplify this some day.
// In the parent, this is just getKey(), but that returns a cumbersome
// key in EditFields. Use the simpler edit type key instead.
$keys[] = $this->getEditTypeKey();
foreach ($this->getAliases() as $alias) {
$keys[] = $alias;
}
return $keys;
}
}
diff --git a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php
index 034f939254..a247f50d9b 100644
--- a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php
+++ b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php
@@ -1,53 +1,58 @@
<?php
final class PhabricatorCustomFieldEditType
extends PhabricatorEditType {
private $customField;
+ private $valueType;
public function setCustomField(PhabricatorCustomField $custom_field) {
$this->customField = $custom_field;
return $this;
}
public function getCustomField() {
return $this->customField;
}
+ public function setValueType($value_type) {
+ $this->valueType = $value_type;
+ return $this;
+ }
+
public function getValueType() {
- // TODO: Improve.
- return 'custom';
+ return $this->valueType;
}
public function getMetadata() {
$field = $this->getCustomField();
return parent::getMetadata() + $field->getApplicationTransactionMetadata();
}
public function getValueDescription() {
$field = $this->getCustomField();
return $field->getFieldDescription();
}
public function generateTransactions(
PhabricatorApplicationTransaction $template,
array $spec) {
$value = idx($spec, 'value');
$xaction = $this->newTransaction($template)
->setNewValue($value);
$custom_type = PhabricatorTransactions::TYPE_CUSTOMFIELD;
if ($xaction->getTransactionType() == $custom_type) {
$field = $this->getCustomField();
$xaction
->setOldValue($field->getOldValueForApplicationTransactions())
->setMetadataValue('customfield:key', $field->getFieldKey());
}
return array($xaction);
}
}
diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php
index 18082c8fce..f3bf787e72 100644
--- a/src/infrastructure/customfield/field/PhabricatorCustomField.php
+++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php
@@ -1,1401 +1,1409 @@
<?php
/**
* @task apps Building Applications with Custom Fields
* @task core Core Properties and Field Identity
* @task proxy Field Proxies
* @task context Contextual Data
* @task render Rendering Utilities
* @task storage Field Storage
* @task edit Integration with Edit Views
* @task view Integration with Property Views
* @task list Integration with List views
* @task appsearch Integration with ApplicationSearch
* @task appxaction Integration with ApplicationTransactions
* @task xactionmail Integration with Transaction Mail
* @task globalsearch Integration with Global Search
* @task herald Integration with Herald
*/
abstract class PhabricatorCustomField extends Phobject {
private $viewer;
private $object;
private $proxy;
const ROLE_APPLICATIONTRANSACTIONS = 'ApplicationTransactions';
const ROLE_TRANSACTIONMAIL = 'ApplicationTransactions.mail';
const ROLE_APPLICATIONSEARCH = 'ApplicationSearch';
const ROLE_STORAGE = 'storage';
const ROLE_DEFAULT = 'default';
const ROLE_EDIT = 'edit';
const ROLE_VIEW = 'view';
const ROLE_LIST = 'list';
const ROLE_GLOBALSEARCH = 'GlobalSearch';
const ROLE_CONDUIT = 'conduit';
const ROLE_HERALD = 'herald';
/* -( Building Applications with Custom Fields )--------------------------- */
/**
* @task apps
*/
public static function getObjectFields(
PhabricatorCustomFieldInterface $object,
$role) {
try {
$attachment = $object->getCustomFields();
} catch (PhabricatorDataNotAttachedException $ex) {
$attachment = new PhabricatorCustomFieldAttachment();
$object->attachCustomFields($attachment);
}
try {
$field_list = $attachment->getCustomFieldList($role);
} catch (PhabricatorCustomFieldNotAttachedException $ex) {
$base_class = $object->getCustomFieldBaseClass();
$spec = $object->getCustomFieldSpecificationForRole($role);
if (!is_array($spec)) {
throw new Exception(
pht(
"Expected an array from %s for object of class '%s'.",
'getCustomFieldSpecificationForRole()',
get_class($object)));
}
$fields = self::buildFieldList(
$base_class,
$spec,
$object);
foreach ($fields as $key => $field) {
if (!$field->shouldEnableForRole($role)) {
unset($fields[$key]);
}
}
foreach ($fields as $field) {
$field->setObject($object);
}
$field_list = new PhabricatorCustomFieldList($fields);
$attachment->addCustomFieldList($role, $field_list);
}
return $field_list;
}
/**
* @task apps
*/
public static function getObjectField(
PhabricatorCustomFieldInterface $object,
$role,
$field_key) {
$fields = self::getObjectFields($object, $role)->getFields();
return idx($fields, $field_key);
}
/**
* @task apps
*/
public static function buildFieldList(
$base_class,
array $spec,
$object,
array $options = array()) {
PhutilTypeSpec::checkMap(
$options,
array(
'withDisabled' => 'optional bool',
));
$field_objects = id(new PhutilSymbolLoader())
->setAncestorClass($base_class)
->loadObjects();
$fields = array();
$from_map = array();
foreach ($field_objects as $field_object) {
$current_class = get_class($field_object);
foreach ($field_object->createFields($object) as $field) {
$key = $field->getFieldKey();
if (isset($fields[$key])) {
throw new Exception(
pht(
"Both '%s' and '%s' define a custom field with ".
"field key '%s'. Field keys must be unique.",
$from_map[$key],
$current_class,
$key));
}
$from_map[$key] = $current_class;
$fields[$key] = $field;
}
}
foreach ($fields as $key => $field) {
if (!$field->isFieldEnabled()) {
unset($fields[$key]);
}
}
$fields = array_select_keys($fields, array_keys($spec)) + $fields;
if (empty($options['withDisabled'])) {
foreach ($fields as $key => $field) {
$config = idx($spec, $key, array()) + array(
'disabled' => $field->shouldDisableByDefault(),
);
if (!empty($config['disabled'])) {
if ($field->canDisableField()) {
unset($fields[$key]);
}
}
}
}
return $fields;
}
/* -( Core Properties and Field Identity )--------------------------------- */
/**
* Return a key which uniquely identifies this field, like
* "mycompany:dinosaur:count". Normally you should provide some level of
* namespacing to prevent collisions.
*
* @return string String which uniquely identifies this field.
* @task core
*/
public function getFieldKey() {
if ($this->proxy) {
return $this->proxy->getFieldKey();
}
throw new PhabricatorCustomFieldImplementationIncompleteException(
$this,
$field_key_is_incomplete = true);
}
/**
* Return a human-readable field name.
*
* @return string Human readable field name.
* @task core
*/
public function getFieldName() {
if ($this->proxy) {
return $this->proxy->getFieldName();
}
return $this->getFieldKey();
}
/**
* Return a short, human-readable description of the field's behavior. This
* provides more context to administrators when they are customizing fields.
*
* @return string|null Optional human-readable description.
* @task core
*/
public function getFieldDescription() {
if ($this->proxy) {
return $this->proxy->getFieldDescription();
}
return null;
}
/**
* Most field implementations are unique, in that one class corresponds to
* one field. However, some field implementations are general and a single
* implementation may drive several fields.
*
* For general implementations, the general field implementation can return
* multiple field instances here.
*
* @param object The object to create fields for.
* @return list<PhabricatorCustomField> List of fields.
* @task core
*/
public function createFields($object) {
return array($this);
}
/**
* You can return `false` here if the field should not be enabled for any
* role. For example, it might depend on something (like an application or
* library) which isn't installed, or might have some global configuration
* which allows it to be disabled.
*
* @return bool False to completely disable this field for all roles.
* @task core
*/
public function isFieldEnabled() {
if ($this->proxy) {
return $this->proxy->isFieldEnabled();
}
return true;
}
/**
* Low level selector for field availability. Fields can appear in different
* roles (like an edit view, a list view, etc.), but not every field needs
* to appear everywhere. Fields that are disabled in a role won't appear in
* that context within applications.
*
* Normally, you do not need to override this method. Instead, override the
* methods specific to roles you want to enable. For example, implement
* @{method:shouldUseStorage()} to activate the `'storage'` role.
*
* @return bool True to enable the field for the given role.
* @task core
*/
public function shouldEnableForRole($role) {
// NOTE: All of these calls proxy individually, so we don't need to
// proxy this call as a whole.
switch ($role) {
case self::ROLE_APPLICATIONTRANSACTIONS:
return $this->shouldAppearInApplicationTransactions();
case self::ROLE_APPLICATIONSEARCH:
return $this->shouldAppearInApplicationSearch();
case self::ROLE_STORAGE:
return $this->shouldUseStorage();
case self::ROLE_EDIT:
return $this->shouldAppearInEditView();
case self::ROLE_VIEW:
return $this->shouldAppearInPropertyView();
case self::ROLE_LIST:
return $this->shouldAppearInListView();
case self::ROLE_GLOBALSEARCH:
return $this->shouldAppearInGlobalSearch();
case self::ROLE_CONDUIT:
return $this->shouldAppearInConduitDictionary();
case self::ROLE_TRANSACTIONMAIL:
return $this->shouldAppearInTransactionMail();
case self::ROLE_HERALD:
return $this->shouldAppearInHerald();
case self::ROLE_DEFAULT:
return true;
default:
throw new Exception(pht("Unknown field role '%s'!", $role));
}
}
/**
* Allow administrators to disable this field. Most fields should allow this,
* but some are fundamental to the behavior of the application and can be
* locked down to avoid chaos, disorder, and the decline of civilization.
*
* @return bool False to prevent this field from being disabled through
* configuration.
* @task core
*/
public function canDisableField() {
return true;
}
public function shouldDisableByDefault() {
return false;
}
/**
* Return an index string which uniquely identifies this field.
*
* @return string Index string which uniquely identifies this field.
* @task core
*/
final public function getFieldIndex() {
return PhabricatorHash::digestForIndex($this->getFieldKey());
}
/* -( Field Proxies )------------------------------------------------------ */
/**
* Proxies allow a field to use some other field's implementation for most
* of their behavior while still subclassing an application field. When a
* proxy is set for a field with @{method:setProxy}, all of its methods will
* call through to the proxy by default.
*
* This is most commonly used to implement configuration-driven custom fields
* using @{class:PhabricatorStandardCustomField}.
*
* This method must be overridden to return `true` before a field can accept
* proxies.
*
* @return bool True if you can @{method:setProxy} this field.
* @task proxy
*/
public function canSetProxy() {
if ($this instanceof PhabricatorStandardCustomFieldInterface) {
return true;
}
return false;
}
/**
* Set the proxy implementation for this field. See @{method:canSetProxy} for
* discussion of field proxies.
*
* @param PhabricatorCustomField Field implementation.
* @return this
*/
final public function setProxy(PhabricatorCustomField $proxy) {
if (!$this->canSetProxy()) {
throw new PhabricatorCustomFieldNotProxyException($this);
}
$this->proxy = $proxy;
return $this;
}
/**
* Get the field's proxy implementation, if any. For discussion, see
* @{method:canSetProxy}.
*
* @return PhabricatorCustomField|null Proxy field, if one is set.
*/
final public function getProxy() {
return $this->proxy;
}
/* -( Contextual Data )---------------------------------------------------- */
/**
* Sets the object this field belongs to.
*
* @param PhabricatorCustomFieldInterface The object this field belongs to.
* @return this
* @task context
*/
final public function setObject(PhabricatorCustomFieldInterface $object) {
if ($this->proxy) {
$this->proxy->setObject($object);
return $this;
}
$this->object = $object;
$this->didSetObject($object);
return $this;
}
/**
* Read object data into local field storage, if applicable.
*
* @param PhabricatorCustomFieldInterface The object this field belongs to.
* @return this
* @task context
*/
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
if ($this->proxy) {
$this->proxy->readValueFromObject($object);
}
return $this;
}
/**
* Get the object this field belongs to.
*
* @return PhabricatorCustomFieldInterface The object this field belongs to.
* @task context
*/
final public function getObject() {
if ($this->proxy) {
return $this->proxy->getObject();
}
return $this->object;
}
/**
* This is a hook, primarily for subclasses to load object data.
*
* @return PhabricatorCustomFieldInterface The object this field belongs to.
* @return void
*/
protected function didSetObject(PhabricatorCustomFieldInterface $object) {
return;
}
/**
* @task context
*/
final public function setViewer(PhabricatorUser $viewer) {
if ($this->proxy) {
$this->proxy->setViewer($viewer);
return $this;
}
$this->viewer = $viewer;
return $this;
}
/**
* @task context
*/
final public function getViewer() {
if ($this->proxy) {
return $this->proxy->getViewer();
}
return $this->viewer;
}
/**
* @task context
*/
final protected function requireViewer() {
if ($this->proxy) {
return $this->proxy->requireViewer();
}
if (!$this->viewer) {
throw new PhabricatorCustomFieldDataNotAvailableException($this);
}
return $this->viewer;
}
/* -( Rendering Utilities )------------------------------------------------ */
/**
* @task render
*/
protected function renderHandleList(array $handles) {
if (!$handles) {
return null;
}
$out = array();
foreach ($handles as $handle) {
$out[] = $handle->renderLink();
}
return phutil_implode_html(phutil_tag('br'), $out);
}
/* -( Storage )------------------------------------------------------------ */
/**
* Return true to use field storage.
*
* Fields which can be edited by the user will most commonly use storage,
* while some other types of fields (for instance, those which just display
* information in some stylized way) may not. Many builtin fields do not use
* storage because their data is available on the object itself.
*
* If you implement this, you must also implement @{method:getValueForStorage}
* and @{method:setValueFromStorage}.
*
* @return bool True to use storage.
* @task storage
*/
public function shouldUseStorage() {
if ($this->proxy) {
return $this->proxy->shouldUseStorage();
}
return false;
}
/**
* Return a new, empty storage object. This should be a subclass of
* @{class:PhabricatorCustomFieldStorage} which is bound to the application's
* database.
*
* @return PhabricatorCustomFieldStorage New empty storage object.
* @task storage
*/
public function newStorageObject() {
// NOTE: This intentionally isn't proxied, to avoid call cycles.
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Return a serialized representation of the field value, appropriate for
* storing in auxiliary field storage. You must implement this method if
* you implement @{method:shouldUseStorage}.
*
* If the field value is a scalar, it can be returned unmodiifed. If not,
* it should be serialized (for example, using JSON).
*
* @return string Serialized field value.
* @task storage
*/
public function getValueForStorage() {
if ($this->proxy) {
return $this->proxy->getValueForStorage();
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Set the field's value given a serialized storage value. This is called
* when the field is loaded; if no data is available, the value will be
* null. You must implement this method if you implement
* @{method:shouldUseStorage}.
*
* Usually, the value can be loaded directly. If it isn't a scalar, you'll
* need to undo whatever serialization you applied in
* @{method:getValueForStorage}.
*
* @param string|null Serialized field representation (from
* @{method:getValueForStorage}) or null if no value has
* ever been stored.
* @return this
* @task storage
*/
public function setValueFromStorage($value) {
if ($this->proxy) {
return $this->proxy->setValueFromStorage($value);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/* -( ApplicationSearch )-------------------------------------------------- */
/**
* Appearing in ApplicationSearch allows a field to be indexed and searched
* for.
*
* @return bool True to appear in ApplicationSearch.
* @task appsearch
*/
public function shouldAppearInApplicationSearch() {
if ($this->proxy) {
return $this->proxy->shouldAppearInApplicationSearch();
}
return false;
}
/**
* Return one or more indexes which this field can meaningfully query against
* to implement ApplicationSearch.
*
* Normally, you should build these using @{method:newStringIndex} and
* @{method:newNumericIndex}. For example, if a field holds a numeric value
* it might return a single numeric index:
*
* return array($this->newNumericIndex($this->getValue()));
*
* If a field holds a more complex value (like a list of users), it might
* return several string indexes:
*
* $indexes = array();
* foreach ($this->getValue() as $phid) {
* $indexes[] = $this->newStringIndex($phid);
* }
* return $indexes;
*
* @return list<PhabricatorCustomFieldIndexStorage> List of indexes.
* @task appsearch
*/
public function buildFieldIndexes() {
if ($this->proxy) {
return $this->proxy->buildFieldIndexes();
}
return array();
}
/**
* Return an index against which this field can be meaningfully ordered
* against to implement ApplicationSearch.
*
* This should be a single index, normally built using
* @{method:newStringIndex} and @{method:newNumericIndex}.
*
* The value of the index is not used.
*
* Return null from this method if the field can not be ordered.
*
* @return PhabricatorCustomFieldIndexStorage A single index to order by.
* @task appsearch
*/
public function buildOrderIndex() {
if ($this->proxy) {
return $this->proxy->buildOrderIndex();
}
return null;
}
/**
* Build a new empty storage object for storing string indexes. Normally,
* this should be a concrete subclass of
* @{class:PhabricatorCustomFieldStringIndexStorage}.
*
* @return PhabricatorCustomFieldStringIndexStorage Storage object.
* @task appsearch
*/
protected function newStringIndexStorage() {
// NOTE: This intentionally isn't proxied, to avoid call cycles.
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Build a new empty storage object for storing string indexes. Normally,
* this should be a concrete subclass of
* @{class:PhabricatorCustomFieldStringIndexStorage}.
*
* @return PhabricatorCustomFieldStringIndexStorage Storage object.
* @task appsearch
*/
protected function newNumericIndexStorage() {
// NOTE: This intentionally isn't proxied, to avoid call cycles.
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Build and populate storage for a string index.
*
* @param string String to index.
* @return PhabricatorCustomFieldStringIndexStorage Populated storage.
* @task appsearch
*/
protected function newStringIndex($value) {
if ($this->proxy) {
return $this->proxy->newStringIndex();
}
$key = $this->getFieldIndex();
return $this->newStringIndexStorage()
->setIndexKey($key)
->setIndexValue($value);
}
/**
* Build and populate storage for a numeric index.
*
* @param string Numeric value to index.
* @return PhabricatorCustomFieldNumericIndexStorage Populated storage.
* @task appsearch
*/
protected function newNumericIndex($value) {
if ($this->proxy) {
return $this->proxy->newNumericIndex();
}
$key = $this->getFieldIndex();
return $this->newNumericIndexStorage()
->setIndexKey($key)
->setIndexValue($value);
}
/**
* Read a query value from a request, for storage in a saved query. Normally,
* this method should, e.g., read a string out of the request.
*
* @param PhabricatorApplicationSearchEngine Engine building the query.
* @param AphrontRequest Request to read from.
* @return wild
* @task appsearch
*/
public function readApplicationSearchValueFromRequest(
PhabricatorApplicationSearchEngine $engine,
AphrontRequest $request) {
if ($this->proxy) {
return $this->proxy->readApplicationSearchValueFromRequest(
$engine,
$request);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Constrain a query, given a field value. Generally, this method should
* use `with...()` methods to apply filters or other constraints to the
* query.
*
* @param PhabricatorApplicationSearchEngine Engine executing the query.
* @param PhabricatorCursorPagedPolicyAwareQuery Query to constrain.
* @param wild Constraint provided by the user.
* @return void
* @task appsearch
*/
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
if ($this->proxy) {
return $this->proxy->applyApplicationSearchConstraintToQuery(
$engine,
$query,
$value);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Append search controls to the interface.
*
* @param PhabricatorApplicationSearchEngine Engine constructing the form.
* @param AphrontFormView The form to update.
* @param wild Value from the saved query.
* @return void
* @task appsearch
*/
public function appendToApplicationSearchForm(
PhabricatorApplicationSearchEngine $engine,
AphrontFormView $form,
$value) {
if ($this->proxy) {
return $this->proxy->appendToApplicationSearchForm(
$engine,
$form,
$value);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/* -( ApplicationTransactions )-------------------------------------------- */
/**
* Appearing in ApplicationTrasactions allows a field to be edited using
* standard workflows.
*
* @return bool True to appear in ApplicationTransactions.
* @task appxaction
*/
public function shouldAppearInApplicationTransactions() {
if ($this->proxy) {
return $this->proxy->shouldAppearInApplicationTransactions();
}
return false;
}
/**
* @task appxaction
*/
public function getApplicationTransactionType() {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionType();
}
return PhabricatorTransactions::TYPE_CUSTOMFIELD;
}
/**
* @task appxaction
*/
public function getApplicationTransactionMetadata() {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionMetadata();
}
return array();
}
/**
* @task appxaction
*/
public function getOldValueForApplicationTransactions() {
if ($this->proxy) {
return $this->proxy->getOldValueForApplicationTransactions();
}
return $this->getValueForStorage();
}
/**
* @task appxaction
*/
public function getNewValueForApplicationTransactions() {
if ($this->proxy) {
return $this->proxy->getNewValueForApplicationTransactions();
}
return $this->getValueForStorage();
}
/**
* @task appxaction
*/
public function setValueFromApplicationTransactions($value) {
if ($this->proxy) {
return $this->proxy->setValueFromApplicationTransactions($value);
}
return $this->setValueFromStorage($value);
}
/**
* @task appxaction
*/
public function getNewValueFromApplicationTransactions(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getNewValueFromApplicationTransactions($xaction);
}
return $xaction->getNewValue();
}
/**
* @task appxaction
*/
public function getApplicationTransactionHasEffect(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionHasEffect($xaction);
}
return ($xaction->getOldValue() !== $xaction->getNewValue());
}
/**
* @task appxaction
*/
public function applyApplicationTransactionInternalEffects(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->applyApplicationTransactionInternalEffects($xaction);
}
return;
}
/**
* @task appxaction
*/
public function getApplicationTransactionRemarkupBlocks(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionRemarkupBlocks($xaction);
}
return array();
}
/**
* @task appxaction
*/
public function applyApplicationTransactionExternalEffects(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->applyApplicationTransactionExternalEffects($xaction);
}
if (!$this->shouldEnableForRole(self::ROLE_STORAGE)) {
return;
}
$this->setValueFromApplicationTransactions($xaction->getNewValue());
$value = $this->getValueForStorage();
$table = $this->newStorageObject();
$conn_w = $table->establishConnection('w');
if ($value === null) {
queryfx(
$conn_w,
'DELETE FROM %T WHERE objectPHID = %s AND fieldIndex = %s',
$table->getTableName(),
$this->getObject()->getPHID(),
$this->getFieldIndex());
} else {
queryfx(
$conn_w,
'INSERT INTO %T (objectPHID, fieldIndex, fieldValue)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE fieldValue = VALUES(fieldValue)',
$table->getTableName(),
$this->getObject()->getPHID(),
$this->getFieldIndex(),
$value);
}
return;
}
/**
* Validate transactions for an object. This allows you to raise an error
* when a transaction would set a field to an invalid value, or when a field
* is required but no transactions provide value.
*
* @param PhabricatorLiskDAO Editor applying the transactions.
* @param string Transaction type. This type is always
* `PhabricatorTransactions::TYPE_CUSTOMFIELD`, it is provided for
* convenience when constructing exceptions.
* @param list<PhabricatorApplicationTransaction> Transactions being applied,
* which may be empty if this field is not being edited.
* @return list<PhabricatorApplicationTransactionValidationError> Validation
* errors.
*
* @task appxaction
*/
public function validateApplicationTransactions(
PhabricatorApplicationTransactionEditor $editor,
$type,
array $xactions) {
if ($this->proxy) {
return $this->proxy->validateApplicationTransactions(
$editor,
$type,
$xactions);
}
return array();
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionTitle(
$xaction);
}
$author_phid = $xaction->getAuthorPHID();
return pht(
'%s updated this object.',
$xaction->renderHandleLink($author_phid));
}
public function getApplicationTransactionTitleForFeed(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionTitleForFeed(
$xaction);
}
$author_phid = $xaction->getAuthorPHID();
$object_phid = $xaction->getObjectPHID();
return pht(
'%s updated %s.',
$xaction->renderHandleLink($author_phid),
$xaction->renderHandleLink($object_phid));
}
public function getApplicationTransactionHasChangeDetails(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionHasChangeDetails(
$xaction);
}
return false;
}
public function getApplicationTransactionChangeDetails(
PhabricatorApplicationTransaction $xaction,
PhabricatorUser $viewer) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionChangeDetails(
$xaction,
$viewer);
}
return null;
}
public function getApplicationTransactionRequiredHandlePHIDs(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionRequiredHandlePHIDs(
$xaction);
}
return array();
}
public function shouldHideInApplicationTransactions(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->shouldHideInApplicationTransactions($xaction);
}
return false;
}
/* -( Transaction Mail )--------------------------------------------------- */
/**
* @task xactionmail
*/
public function shouldAppearInTransactionMail() {
if ($this->proxy) {
return $this->proxy->shouldAppearInTransactionMail();
}
return false;
}
/**
* @task xactionmail
*/
public function updateTransactionMailBody(
PhabricatorMetaMTAMailBody $body,
PhabricatorApplicationTransactionEditor $editor,
array $xactions) {
if ($this->proxy) {
return $this->proxy->updateTransactionMailBody($body, $editor, $xactions);
}
return;
}
/* -( Edit View )---------------------------------------------------------- */
public function getEditEngineFields(PhabricatorEditEngine $engine) {
$field = $this->newStandardEditField($engine);
return array(
$field,
);
}
protected function newEditField() {
$field = id(new PhabricatorCustomFieldEditField())
->setCustomField($this);
$http_type = $this->getHTTPParameterType();
if ($http_type) {
$field->setCustomFieldHTTPParameterType($http_type);
}
return $field;
}
protected function newStandardEditField() {
if ($this->proxy) {
return $this->proxy->newStandardEditField();
}
return $this->newEditField()
->setKey($this->getFieldKey())
->setEditTypeKey('custom.'.$this->getFieldKey())
->setLabel($this->getFieldName())
->setDescription($this->getFieldDescription())
->setTransactionType($this->getApplicationTransactionType())
->setValue($this->getNewValueForApplicationTransactions());
}
protected function getHTTPParameterType() {
if ($this->proxy) {
return $this->proxy->getHTTPParameterType();
}
return null;
}
/**
* @task edit
*/
public function shouldAppearInEditView() {
if ($this->proxy) {
return $this->proxy->shouldAppearInEditView();
}
return false;
}
/**
* @task edit
*/
public function readValueFromRequest(AphrontRequest $request) {
if ($this->proxy) {
return $this->proxy->readValueFromRequest($request);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* @task edit
*/
public function getRequiredHandlePHIDsForEdit() {
if ($this->proxy) {
return $this->proxy->getRequiredHandlePHIDsForEdit();
}
return array();
}
/**
* @task edit
*/
public function getInstructionsForEdit() {
if ($this->proxy) {
return $this->proxy->getInstructionsForEdit();
}
return null;
}
/**
* @task edit
*/
public function renderEditControl(array $handles) {
if ($this->proxy) {
return $this->proxy->renderEditControl($handles);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/* -( Property View )------------------------------------------------------ */
/**
* @task view
*/
public function shouldAppearInPropertyView() {
if ($this->proxy) {
return $this->proxy->shouldAppearInPropertyView();
}
return false;
}
/**
* @task view
*/
public function renderPropertyViewLabel() {
if ($this->proxy) {
return $this->proxy->renderPropertyViewLabel();
}
return $this->getFieldName();
}
/**
* @task view
*/
public function renderPropertyViewValue(array $handles) {
if ($this->proxy) {
return $this->proxy->renderPropertyViewValue($handles);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* @task view
*/
public function getStyleForPropertyView() {
if ($this->proxy) {
return $this->proxy->getStyleForPropertyView();
}
return 'property';
}
/**
* @task view
*/
public function getIconForPropertyView() {
if ($this->proxy) {
return $this->proxy->getIconForPropertyView();
}
return null;
}
/**
* @task view
*/
public function getRequiredHandlePHIDsForPropertyView() {
if ($this->proxy) {
return $this->proxy->getRequiredHandlePHIDsForPropertyView();
}
return array();
}
/* -( List View )---------------------------------------------------------- */
/**
* @task list
*/
public function shouldAppearInListView() {
if ($this->proxy) {
return $this->proxy->shouldAppearInListView();
}
return false;
}
/**
* @task list
*/
public function renderOnListItem(PHUIObjectItemView $view) {
if ($this->proxy) {
return $this->proxy->renderOnListItem($view);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/* -( Global Search )------------------------------------------------------ */
/**
* @task globalsearch
*/
public function shouldAppearInGlobalSearch() {
if ($this->proxy) {
return $this->proxy->shouldAppearInGlobalSearch();
}
return false;
}
/**
* @task globalsearch
*/
public function updateAbstractDocument(
PhabricatorSearchAbstractDocument $document) {
if ($this->proxy) {
return $this->proxy->updateAbstractDocument($document);
}
return $document;
}
/* -( Conduit )------------------------------------------------------------ */
/**
* @task conduit
*/
public function shouldAppearInConduitDictionary() {
if ($this->proxy) {
return $this->proxy->shouldAppearInConduitDictionary();
}
return false;
}
/**
* @task conduit
*/
public function getConduitDictionaryValue() {
if ($this->proxy) {
return $this->proxy->getConduitDictionaryValue();
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
+ public function shouldAppearInConduitTransactions() {
+ if ($this->proxy) {
+ return $this->proxy->shouldAppearInConduitDictionary();
+ }
+ return false;
+ }
+
+
/* -( Herald )------------------------------------------------------------- */
/**
* Return `true` to make this field available in Herald.
*
* @return bool True to expose the field in Herald.
* @task herald
*/
public function shouldAppearInHerald() {
if ($this->proxy) {
return $this->proxy->shouldAppearInHerald();
}
return false;
}
/**
* Get the name of the field in Herald. By default, this uses the
* normal field name.
*
* @return string Herald field name.
* @task herald
*/
public function getHeraldFieldName() {
if ($this->proxy) {
return $this->proxy->getHeraldFieldName();
}
return $this->getFieldName();
}
/**
* Get the field value for evaluation by Herald.
*
* @return wild Field value.
* @task herald
*/
public function getHeraldFieldValue() {
if ($this->proxy) {
return $this->proxy->getHeraldFieldValue();
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Get the available conditions for this field in Herald.
*
* @return list<const> List of Herald condition constants.
* @task herald
*/
public function getHeraldFieldConditions() {
if ($this->proxy) {
return $this->proxy->getHeraldFieldConditions();
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Get the Herald value type for the given condition.
*
* @param const Herald condition constant.
* @return const|null Herald value type, or null to use the default.
* @task herald
*/
public function getHeraldFieldValueType($condition) {
if ($this->proxy) {
return $this->proxy->getHeraldFieldValueType($condition);
}
return null;
}
}
diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
index 66f85bd04d..1e081f5cfe 100644
--- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
+++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
@@ -1,436 +1,440 @@
<?php
abstract class PhabricatorStandardCustomField
extends PhabricatorCustomField {
private $rawKey;
private $fieldKey;
private $fieldName;
private $fieldValue;
private $fieldDescription;
private $fieldConfig;
private $applicationField;
private $strings = array();
private $caption;
private $fieldError;
private $required;
private $default;
abstract public function getFieldType();
public static function buildStandardFields(
PhabricatorCustomField $template,
array $config) {
$types = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getFieldType')
->execute();
$fields = array();
foreach ($config as $key => $value) {
$type = idx($value, 'type', 'text');
if (empty($types[$type])) {
// TODO: We should have better typechecking somewhere, and then make
// this more serious.
continue;
}
$namespace = $template->getStandardCustomFieldNamespace();
$full_key = "std:{$namespace}:{$key}";
$template = clone $template;
$standard = id(clone $types[$type])
->setRawStandardFieldKey($key)
->setFieldKey($full_key)
->setFieldConfig($value)
->setApplicationField($template);
$field = $template->setProxy($standard);
$fields[] = $field;
}
return $fields;
}
public function setApplicationField(
PhabricatorStandardCustomFieldInterface $application_field) {
$this->applicationField = $application_field;
return $this;
}
public function getApplicationField() {
return $this->applicationField;
}
public function setFieldName($name) {
$this->fieldName = $name;
return $this;
}
public function getFieldValue() {
return $this->fieldValue;
}
public function setFieldValue($value) {
$this->fieldValue = $value;
return $this;
}
public function setCaption($caption) {
$this->caption = $caption;
return $this;
}
public function getCaption() {
return $this->caption;
}
public function setFieldDescription($description) {
$this->fieldDescription = $description;
return $this;
}
public function setFieldConfig(array $config) {
foreach ($config as $key => $value) {
switch ($key) {
case 'name':
$this->setFieldName($value);
break;
case 'description':
$this->setFieldDescription($value);
break;
case 'strings':
$this->setStrings($value);
break;
case 'caption':
$this->setCaption($value);
break;
case 'required':
if ($value) {
$this->setRequired($value);
$this->setFieldError(true);
}
break;
case 'default':
$this->setFieldValue($value);
break;
case 'type':
// We set this earlier on.
break;
}
}
$this->fieldConfig = $config;
return $this;
}
public function getFieldConfigValue($key, $default = null) {
return idx($this->fieldConfig, $key, $default);
}
public function setFieldError($field_error) {
$this->fieldError = $field_error;
return $this;
}
public function getFieldError() {
return $this->fieldError;
}
public function setRequired($required) {
$this->required = $required;
return $this;
}
public function getRequired() {
return $this->required;
}
public function setRawStandardFieldKey($raw_key) {
$this->rawKey = $raw_key;
return $this;
}
public function getRawStandardFieldKey() {
return $this->rawKey;
}
/* -( PhabricatorCustomField )--------------------------------------------- */
public function setFieldKey($field_key) {
$this->fieldKey = $field_key;
return $this;
}
public function getFieldKey() {
return $this->fieldKey;
}
public function getFieldName() {
return coalesce($this->fieldName, parent::getFieldName());
}
public function getFieldDescription() {
return coalesce($this->fieldDescription, parent::getFieldDescription());
}
public function setStrings(array $strings) {
$this->strings = $strings;
return;
}
public function getString($key, $default = null) {
return idx($this->strings, $key, $default);
}
public function shouldUseStorage() {
try {
$object = $this->newStorageObject();
return true;
} catch (PhabricatorCustomFieldImplementationIncompleteException $ex) {
return false;
}
}
public function getValueForStorage() {
return $this->getFieldValue();
}
public function setValueFromStorage($value) {
return $this->setFieldValue($value);
}
public function shouldAppearInApplicationTransactions() {
return true;
}
public function shouldAppearInEditView() {
return $this->getFieldConfigValue('edit', true);
}
public function readValueFromRequest(AphrontRequest $request) {
$value = $request->getStr($this->getFieldKey());
if (!strlen($value)) {
$value = null;
}
$this->setFieldValue($value);
}
public function getInstructionsForEdit() {
return $this->getFieldConfigValue('instructions');
}
public function getPlaceholder() {
return $this->getFieldConfigValue('placeholder', null);
}
public function renderEditControl(array $handles) {
return id(new AphrontFormTextControl())
->setName($this->getFieldKey())
->setCaption($this->getCaption())
->setValue($this->getFieldValue())
->setError($this->getFieldError())
->setLabel($this->getFieldName())
->setPlaceholder($this->getPlaceholder());
}
public function newStorageObject() {
return $this->getApplicationField()->newStorageObject();
}
public function shouldAppearInPropertyView() {
return $this->getFieldConfigValue('view', true);
}
public function renderPropertyViewValue(array $handles) {
if (!strlen($this->getFieldValue())) {
return null;
}
return $this->getFieldValue();
}
public function shouldAppearInApplicationSearch() {
return $this->getFieldConfigValue('search', false);
}
protected function newStringIndexStorage() {
return $this->getApplicationField()->newStringIndexStorage();
}
protected function newNumericIndexStorage() {
return $this->getApplicationField()->newNumericIndexStorage();
}
public function buildFieldIndexes() {
return array();
}
public function buildOrderIndex() {
return null;
}
public function readApplicationSearchValueFromRequest(
PhabricatorApplicationSearchEngine $engine,
AphrontRequest $request) {
return;
}
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
return;
}
public function appendToApplicationSearchForm(
PhabricatorApplicationSearchEngine $engine,
AphrontFormView $form,
$value) {
return;
}
public function validateApplicationTransactions(
PhabricatorApplicationTransactionEditor $editor,
$type,
array $xactions) {
$this->setFieldError(null);
$errors = parent::validateApplicationTransactions(
$editor,
$type,
$xactions);
if ($this->getRequired()) {
$value = $this->getOldValueForApplicationTransactions();
$transaction = null;
foreach ($xactions as $xaction) {
$value = $xaction->getNewValue();
if (!$this->isValueEmpty($value)) {
$transaction = $xaction;
break;
}
}
if ($this->isValueEmpty($value)) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('%s is required.', $this->getFieldName()),
$transaction);
$error->setIsMissingFieldError(true);
$errors[] = $error;
$this->setFieldError(pht('Required'));
}
}
return $errors;
}
protected function isValueEmpty($value) {
if (is_array($value)) {
return empty($value);
}
return !strlen($value);
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if (!$old) {
return pht(
'%s set %s to %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$new);
} else if (!$new) {
return pht(
'%s removed %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName());
} else {
return pht(
'%s changed %s from %s to %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$old,
$new);
}
}
public function getApplicationTransactionTitleForFeed(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$object_phid = $xaction->getObjectPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
if (!$old) {
return pht(
'%s set %s to %s on %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$new,
$xaction->renderHandleLink($object_phid));
} else if (!$new) {
return pht(
'%s removed %s on %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$xaction->renderHandleLink($object_phid));
} else {
return pht(
'%s changed %s from %s to %s on %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$old,
$new,
$xaction->renderHandleLink($object_phid));
}
}
public function getHeraldFieldValue() {
return $this->getFieldValue();
}
public function getFieldControlID($key = null) {
$key = coalesce($key, $this->getRawStandardFieldKey());
return 'std:control:'.$key;
}
public function shouldAppearInGlobalSearch() {
return $this->getFieldConfigValue('fulltext', false);
}
public function updateAbstractDocument(
PhabricatorSearchAbstractDocument $document) {
$field_key = $this->getFieldConfigValue('fulltext');
// If the caller or configuration didn't specify a valid field key,
// generate one automatically from the field index.
if (!is_string($field_key) || (strlen($field_key) != 4)) {
$field_key = '!'.substr($this->getFieldIndex(), 0, 3);
}
$field_value = $this->getFieldValue();
if (strlen($field_value)) {
$document->addField($field_key, $field_value);
}
}
protected function newStandardEditField() {
$short = 'custom.'.$this->getRawStandardFieldKey();
return parent::newStandardEditField()
->setEditTypeKey($short);
}
+ public function shouldAppearInConduitTransactions() {
+ return true;
+ }
+
}
diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php
index c5b86bb097..eb6ea96bf4 100644
--- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php
+++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php
@@ -1,194 +1,200 @@
<?php
final class PhabricatorStandardCustomFieldDate
extends PhabricatorStandardCustomField {
public function getFieldType() {
return 'date';
}
public function buildFieldIndexes() {
$indexes = array();
$value = $this->getFieldValue();
if (strlen($value)) {
$indexes[] = $this->newNumericIndex((int)$value);
}
return $indexes;
}
public function buildOrderIndex() {
return $this->newNumericIndex(0);
}
public function getValueForStorage() {
$value = $this->getFieldValue();
if (strlen($value)) {
return (int)$value;
} else {
return null;
}
}
public function setValueFromStorage($value) {
if (strlen($value)) {
$value = (int)$value;
} else {
$value = null;
}
return $this->setFieldValue($value);
}
public function renderEditControl(array $handles) {
return $this->newDateControl();
}
public function readValueFromRequest(AphrontRequest $request) {
$control = $this->newDateControl();
$control->setUser($request->getUser());
$value = $control->readValueFromRequest($request);
$this->setFieldValue($value);
}
public function renderPropertyViewValue(array $handles) {
$value = $this->getFieldValue();
if (!$value) {
return null;
}
return phabricator_datetime($value, $this->getViewer());
}
private function newDateControl() {
$control = id(new AphrontFormDateControl())
->setLabel($this->getFieldName())
->setName($this->getFieldKey())
->setUser($this->getViewer())
->setCaption($this->getCaption())
->setAllowNull(!$this->getRequired());
// If the value is already numeric, treat it as an epoch timestamp and set
// it directly. Otherwise, it's likely a field default, which we let users
// specify as a string. Parse the string into an epoch.
$value = $this->getFieldValue();
if (!ctype_digit($value)) {
$value = PhabricatorTime::parseLocalTime($value, $this->getViewer());
}
// If we don't have anything valid, make sure we pass `null`, since the
// control special-cases that.
$control->setValue(nonempty($value, null));
return $control;
}
public function readApplicationSearchValueFromRequest(
PhabricatorApplicationSearchEngine $engine,
AphrontRequest $request) {
$key = $this->getFieldKey();
return array(
'min' => $request->getStr($key.'.min'),
'max' => $request->getStr($key.'.max'),
);
}
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
$viewer = $this->getViewer();
if (!is_array($value)) {
$value = array();
}
$min_str = idx($value, 'min', '');
if (strlen($min_str)) {
$min = PhabricatorTime::parseLocalTime($min_str, $viewer);
} else {
$min = null;
}
$max_str = idx($value, 'max', '');
if (strlen($max_str)) {
$max = PhabricatorTime::parseLocalTime($max_str, $viewer);
} else {
$max = null;
}
if (($min !== null) || ($max !== null)) {
$query->withApplicationSearchRangeConstraint(
$this->newNumericIndex(null),
$min,
$max);
}
}
public function appendToApplicationSearchForm(
PhabricatorApplicationSearchEngine $engine,
AphrontFormView $form,
$value) {
if (!is_array($value)) {
$value = array();
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('%s After', $this->getFieldName()))
->setName($this->getFieldKey().'.min')
->setValue(idx($value, 'min', '')))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('%s Before', $this->getFieldName()))
->setName($this->getFieldKey().'.max')
->setValue(idx($value, 'max', '')));
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$viewer = $this->getViewer();
$old_date = null;
if ($old) {
$old_date = phabricator_datetime($old, $viewer);
}
$new_date = null;
if ($new) {
$new_date = phabricator_datetime($new, $viewer);
}
if (!$old) {
return pht(
'%s set %s to %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$new_date);
} else if (!$new) {
return pht(
'%s removed %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName());
} else {
return pht(
'%s changed %s from %s to %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$old_date,
$new_date);
}
}
+ public function shouldAppearInConduitTransactions() {
+ // TODO: Dates are complicated and we don't yet support handling them from
+ // Conduit.
+ return false;
+ }
+
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Apr 30, 8:38 PM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
108800
Default Alt Text
(65 KB)

Event Timeline