Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / src / infrastructure / customfield / field / PhabricatorCustomFieldList.php
blobd59ccb11449fa354be8bf3ec62bdccc6601d21ce
1 <?php
3 /**
4 * Convenience class to perform operations on an entire field list, like reading
5 * all values from storage.
7 * $field_list = new PhabricatorCustomFieldList($fields);
9 */
10 final class PhabricatorCustomFieldList extends Phobject {
12 private $fields;
13 private $viewer;
15 public function __construct(array $fields) {
16 assert_instances_of($fields, 'PhabricatorCustomField');
17 $this->fields = $fields;
20 public function getFields() {
21 return $this->fields;
24 public function setViewer(PhabricatorUser $viewer) {
25 $this->viewer = $viewer;
26 foreach ($this->getFields() as $field) {
27 $field->setViewer($viewer);
29 return $this;
32 public function readFieldsFromObject(
33 PhabricatorCustomFieldInterface $object) {
35 $fields = $this->getFields();
37 foreach ($fields as $field) {
38 $field
39 ->setObject($object)
40 ->readValueFromObject($object);
43 return $this;
46 /**
47 * Read stored values for all fields which support storage.
49 * @param PhabricatorCustomFieldInterface Object to read field values for.
50 * @return void
52 public function readFieldsFromStorage(
53 PhabricatorCustomFieldInterface $object) {
55 $this->readFieldsFromObject($object);
57 $fields = $this->getFields();
58 id(new PhabricatorCustomFieldStorageQuery())
59 ->addFields($fields)
60 ->execute();
62 return $this;
65 public function appendFieldsToForm(AphrontFormView $form) {
66 $enabled = array();
67 foreach ($this->fields as $field) {
68 if ($field->shouldEnableForRole(PhabricatorCustomField::ROLE_EDIT)) {
69 $enabled[] = $field;
73 $phids = array();
74 foreach ($enabled as $field_key => $field) {
75 $phids[$field_key] = $field->getRequiredHandlePHIDsForEdit();
78 $all_phids = array_mergev($phids);
79 if ($all_phids) {
80 $handles = id(new PhabricatorHandleQuery())
81 ->setViewer($this->viewer)
82 ->withPHIDs($all_phids)
83 ->execute();
84 } else {
85 $handles = array();
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
114 // of properties.
115 $head = array();
116 $tail = array();
117 foreach ($fields as $key => $field) {
118 $style = $field->getStyleForPropertyView();
119 switch ($style) {
120 case 'property':
121 case 'header':
122 $head[$key] = $field;
123 break;
124 case 'block':
125 $tail[$key] = $field;
126 break;
127 default:
128 throw new Exception(
129 pht(
130 "Unknown field property view style '%s'; valid styles are ".
131 "'%s' and '%s'.",
132 $style,
133 'block',
134 'property'));
137 $fields = $head + $tail;
139 $add_header = null;
141 $phids = array();
142 foreach ($fields as $key => $field) {
143 $phids[$key] = $field->getRequiredHandlePHIDsForPropertyView();
146 $all_phids = array_mergev($phids);
147 if ($all_phids) {
148 $handles = id(new PhabricatorHandleQuery())
149 ->setViewer($viewer)
150 ->withPHIDs($all_phids)
151 ->execute();
152 } else {
153 $handles = array();
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()) {
162 case 'header':
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
165 // list like this:
167 // Header A
168 // Prop A: Value A
169 // Header B
170 // Prop B: Value B
172 // ...if the "Prop A" field returns `null` when rendering its
173 // property value and we rendered naively, we'd get this:
175 // Header A
176 // Header B
177 // Prop B: Value B
179 // This is silly. Instead, we hide "Header A".
180 $add_header = $value;
181 break;
182 case 'property':
183 if ($add_header !== null) {
184 // Add the most recently seen header.
185 $view->addSectionHeader($add_header);
186 $add_header = null;
188 $view->addProperty($label, $value);
189 break;
190 case 'block':
191 $icon = $field->getIconForPropertyView();
192 $view->invokeWillRenderEvent();
193 if ($label !== null) {
194 $view->addSectionHeader($label, $icon);
196 $view->addTextContent($value);
197 break;
203 public function buildFieldTransactionsFromRequest(
204 PhabricatorApplicationTransaction $template,
205 AphrontRequest $request) {
207 $xactions = array();
209 $role = PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS;
210 foreach ($this->fields as $field) {
211 if (!$field->shouldEnableForRole($role)) {
212 continue;
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
221 // as an input.
222 $old_value = $field->getOldValueForApplicationTransactions();
223 $xaction->setOldValue($old_value);
226 $field->readValueFromRequest($request);
228 $xaction
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;
244 return $xactions;
249 * Publish field indexes into index tables, so ApplicationSearch can search
250 * them.
252 * @return void
254 public function rebuildIndexes(PhabricatorCustomFieldInterface $object) {
255 $indexes = array();
256 $index_keys = array();
258 $phid = $object->getPHID();
260 $role = PhabricatorCustomField::ROLE_APPLICATIONSEARCH;
261 foreach ($this->fields as $field) {
262 if (!$field->shouldEnableForRole($role)) {
263 continue;
266 $index_keys[$field->getFieldIndex()] = true;
268 foreach ($field->buildFieldIndexes() as $index) {
269 $index->setObjectPHID($phid);
270 $indexes[$index->getTableName()][] = $index;
274 if (!$indexes) {
275 return;
278 $any_index = head(head($indexes));
279 $conn_w = $any_index->establishConnection('w');
281 foreach ($indexes as $table => $index_list) {
282 $sql = array();
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) {
292 queryfx(
293 $conn_w,
294 'DELETE FROM %T WHERE objectPHID = %s AND indexKey IN (%Ls)',
295 $table,
296 $phid,
297 array_keys($index_keys));
299 if (!$sql_list) {
300 continue;
303 foreach (PhabricatorLiskDAO::chunkSQL($sql_list) as $chunk) {
304 queryfx(
305 $conn_w,
306 'INSERT INTO %T (objectPHID, indexKey, indexValue) VALUES %LQ',
307 $table,
308 $chunk);
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)) {
321 continue;
323 $field->updateAbstractDocument($document);