4 * Convenience class to perform operations on an entire field list, like reading
5 * all values from storage.
7 * $field_list = new PhabricatorCustomFieldList($fields);
10 final class PhabricatorCustomFieldList
extends Phobject
{
15 public function __construct(array $fields) {
16 assert_instances_of($fields, 'PhabricatorCustomField');
17 $this->fields
= $fields;
20 public function getFields() {
24 public function setViewer(PhabricatorUser
$viewer) {
25 $this->viewer
= $viewer;
26 foreach ($this->getFields() as $field) {
27 $field->setViewer($viewer);
32 public function readFieldsFromObject(
33 PhabricatorCustomFieldInterface
$object) {
35 $fields = $this->getFields();
37 foreach ($fields as $field) {
40 ->readValueFromObject($object);
47 * Read stored values for all fields which support storage.
49 * @param PhabricatorCustomFieldInterface Object to read field values for.
52 public function readFieldsFromStorage(
53 PhabricatorCustomFieldInterface
$object) {
55 $this->readFieldsFromObject($object);
57 $fields = $this->getFields();
58 id(new PhabricatorCustomFieldStorageQuery())
65 public function appendFieldsToForm(AphrontFormView
$form) {
67 foreach ($this->fields
as $field) {
68 if ($field->shouldEnableForRole(PhabricatorCustomField
::ROLE_EDIT
)) {
74 foreach ($enabled as $field_key => $field) {
75 $phids[$field_key] = $field->getRequiredHandlePHIDsForEdit();
78 $all_phids = array_mergev($phids);
80 $handles = id(new PhabricatorHandleQuery())
81 ->setViewer($this->viewer
)
82 ->withPHIDs($all_phids)
88 foreach ($enabled as $field_key => $field) {
89 $field_handles = array_select_keys($handles, $phids[$field_key]);
91 $instructions = $field->getInstructionsForEdit();
92 if ($instructions !== null && strlen($instructions)) {
93 $form->appendRemarkupInstructions($instructions);
96 $form->appendChild($field->renderEditControl($field_handles));
100 public function appendFieldsToPropertyList(
101 PhabricatorCustomFieldInterface
$object,
102 PhabricatorUser
$viewer,
103 PHUIPropertyListView
$view) {
105 $this->readFieldsFromStorage($object);
106 $fields = $this->fields
;
108 foreach ($fields as $field) {
109 $field->setViewer($viewer);
112 // Move all the blocks to the end, regardless of their configuration order,
113 // because it always looks silly to render a block in the middle of a list
117 foreach ($fields as $key => $field) {
118 $style = $field->getStyleForPropertyView();
122 $head[$key] = $field;
125 $tail[$key] = $field;
130 "Unknown field property view style '%s'; valid styles are ".
137 $fields = $head +
$tail;
142 foreach ($fields as $key => $field) {
143 $phids[$key] = $field->getRequiredHandlePHIDsForPropertyView();
146 $all_phids = array_mergev($phids);
148 $handles = id(new PhabricatorHandleQuery())
150 ->withPHIDs($all_phids)
156 foreach ($fields as $key => $field) {
157 $field_handles = array_select_keys($handles, $phids[$key]);
158 $label = $field->renderPropertyViewLabel();
159 $value = $field->renderPropertyViewValue($field_handles);
160 if ($value !== null) {
161 switch ($field->getStyleForPropertyView()) {
163 // We want to hide headers if the fields they're associated with
164 // don't actually produce any visible properties. For example, in a
172 // ...if the "Prop A" field returns `null` when rendering its
173 // property value and we rendered naively, we'd get this:
179 // This is silly. Instead, we hide "Header A".
180 $add_header = $value;
183 if ($add_header !== null) {
184 // Add the most recently seen header.
185 $view->addSectionHeader($add_header);
188 $view->addProperty($label, $value);
191 $icon = $field->getIconForPropertyView();
192 $view->invokeWillRenderEvent();
193 if ($label !== null) {
194 $view->addSectionHeader($label, $icon);
196 $view->addTextContent($value);
203 public function buildFieldTransactionsFromRequest(
204 PhabricatorApplicationTransaction
$template,
205 AphrontRequest
$request) {
209 $role = PhabricatorCustomField
::ROLE_APPLICATIONTRANSACTIONS
;
210 foreach ($this->fields
as $field) {
211 if (!$field->shouldEnableForRole($role)) {
215 $transaction_type = $field->getApplicationTransactionType();
216 $xaction = id(clone $template)
217 ->setTransactionType($transaction_type);
219 if ($transaction_type == PhabricatorTransactions
::TYPE_CUSTOMFIELD
) {
220 // For TYPE_CUSTOMFIELD transactions only, we provide the old value
222 $old_value = $field->getOldValueForApplicationTransactions();
223 $xaction->setOldValue($old_value);
226 $field->readValueFromRequest($request);
229 ->setNewValue($field->getNewValueForApplicationTransactions());
231 if ($transaction_type == PhabricatorTransactions
::TYPE_CUSTOMFIELD
) {
232 // For TYPE_CUSTOMFIELD transactions, add the field key in metadata.
233 $xaction->setMetadataValue('customfield:key', $field->getFieldKey());
236 $metadata = $field->getApplicationTransactionMetadata();
237 foreach ($metadata as $key => $value) {
238 $xaction->setMetadataValue($key, $value);
241 $xactions[] = $xaction;
249 * Publish field indexes into index tables, so ApplicationSearch can search
254 public function rebuildIndexes(PhabricatorCustomFieldInterface
$object) {
256 $index_keys = array();
258 $phid = $object->getPHID();
260 $role = PhabricatorCustomField
::ROLE_APPLICATIONSEARCH
;
261 foreach ($this->fields
as $field) {
262 if (!$field->shouldEnableForRole($role)) {
266 $index_keys[$field->getFieldIndex()] = true;
268 foreach ($field->buildFieldIndexes() as $index) {
269 $index->setObjectPHID($phid);
270 $indexes[$index->getTableName()][] = $index;
278 $any_index = head(head($indexes));
279 $conn_w = $any_index->establishConnection('w');
281 foreach ($indexes as $table => $index_list) {
283 foreach ($index_list as $index) {
284 $sql[] = $index->formatForInsert($conn_w);
286 $indexes[$table] = $sql;
289 $any_index->openTransaction();
291 foreach ($indexes as $table => $sql_list) {
294 'DELETE FROM %T WHERE objectPHID = %s AND indexKey IN (%Ls)',
297 array_keys($index_keys));
303 foreach (PhabricatorLiskDAO
::chunkSQL($sql_list) as $chunk) {
306 'INSERT INTO %T (objectPHID, indexKey, indexValue) VALUES %LQ',
312 $any_index->saveTransaction();
315 public function updateAbstractDocument(
316 PhabricatorSearchAbstractDocument
$document) {
318 $role = PhabricatorCustomField
::ROLE_GLOBALSEARCH
;
319 foreach ($this->getFields() as $field) {
320 if (!$field->shouldEnableForRole($role)) {
323 $field->updateAbstractDocument($document);