Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php
12242 views
1
<?php
2
3
/**
4
* Convenience class to perform operations on an entire field list, like reading
5
* all values from storage.
6
*
7
* $field_list = new PhabricatorCustomFieldList($fields);
8
*
9
*/
10
final class PhabricatorCustomFieldList extends Phobject {
11
12
private $fields;
13
private $viewer;
14
15
public function __construct(array $fields) {
16
assert_instances_of($fields, 'PhabricatorCustomField');
17
$this->fields = $fields;
18
}
19
20
public function getFields() {
21
return $this->fields;
22
}
23
24
public function setViewer(PhabricatorUser $viewer) {
25
$this->viewer = $viewer;
26
foreach ($this->getFields() as $field) {
27
$field->setViewer($viewer);
28
}
29
return $this;
30
}
31
32
public function readFieldsFromObject(
33
PhabricatorCustomFieldInterface $object) {
34
35
$fields = $this->getFields();
36
37
foreach ($fields as $field) {
38
$field
39
->setObject($object)
40
->readValueFromObject($object);
41
}
42
43
return $this;
44
}
45
46
/**
47
* Read stored values for all fields which support storage.
48
*
49
* @param PhabricatorCustomFieldInterface Object to read field values for.
50
* @return void
51
*/
52
public function readFieldsFromStorage(
53
PhabricatorCustomFieldInterface $object) {
54
55
$this->readFieldsFromObject($object);
56
57
$fields = $this->getFields();
58
id(new PhabricatorCustomFieldStorageQuery())
59
->addFields($fields)
60
->execute();
61
62
return $this;
63
}
64
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;
70
}
71
}
72
73
$phids = array();
74
foreach ($enabled as $field_key => $field) {
75
$phids[$field_key] = $field->getRequiredHandlePHIDsForEdit();
76
}
77
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();
86
}
87
88
foreach ($enabled as $field_key => $field) {
89
$field_handles = array_select_keys($handles, $phids[$field_key]);
90
91
$instructions = $field->getInstructionsForEdit();
92
if ($instructions !== null && strlen($instructions)) {
93
$form->appendRemarkupInstructions($instructions);
94
}
95
96
$form->appendChild($field->renderEditControl($field_handles));
97
}
98
}
99
100
public function appendFieldsToPropertyList(
101
PhabricatorCustomFieldInterface $object,
102
PhabricatorUser $viewer,
103
PHUIPropertyListView $view) {
104
105
$this->readFieldsFromStorage($object);
106
$fields = $this->fields;
107
108
foreach ($fields as $field) {
109
$field->setViewer($viewer);
110
}
111
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'));
135
}
136
}
137
$fields = $head + $tail;
138
139
$add_header = null;
140
141
$phids = array();
142
foreach ($fields as $key => $field) {
143
$phids[$key] = $field->getRequiredHandlePHIDsForPropertyView();
144
}
145
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();
154
}
155
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:
166
//
167
// Header A
168
// Prop A: Value A
169
// Header B
170
// Prop B: Value B
171
//
172
// ...if the "Prop A" field returns `null` when rendering its
173
// property value and we rendered naively, we'd get this:
174
//
175
// Header A
176
// Header B
177
// Prop B: Value B
178
//
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;
187
}
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);
195
}
196
$view->addTextContent($value);
197
break;
198
}
199
}
200
}
201
}
202
203
public function buildFieldTransactionsFromRequest(
204
PhabricatorApplicationTransaction $template,
205
AphrontRequest $request) {
206
207
$xactions = array();
208
209
$role = PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS;
210
foreach ($this->fields as $field) {
211
if (!$field->shouldEnableForRole($role)) {
212
continue;
213
}
214
215
$transaction_type = $field->getApplicationTransactionType();
216
$xaction = id(clone $template)
217
->setTransactionType($transaction_type);
218
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);
224
}
225
226
$field->readValueFromRequest($request);
227
228
$xaction
229
->setNewValue($field->getNewValueForApplicationTransactions());
230
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());
234
}
235
236
$metadata = $field->getApplicationTransactionMetadata();
237
foreach ($metadata as $key => $value) {
238
$xaction->setMetadataValue($key, $value);
239
}
240
241
$xactions[] = $xaction;
242
}
243
244
return $xactions;
245
}
246
247
248
/**
249
* Publish field indexes into index tables, so ApplicationSearch can search
250
* them.
251
*
252
* @return void
253
*/
254
public function rebuildIndexes(PhabricatorCustomFieldInterface $object) {
255
$indexes = array();
256
$index_keys = array();
257
258
$phid = $object->getPHID();
259
260
$role = PhabricatorCustomField::ROLE_APPLICATIONSEARCH;
261
foreach ($this->fields as $field) {
262
if (!$field->shouldEnableForRole($role)) {
263
continue;
264
}
265
266
$index_keys[$field->getFieldIndex()] = true;
267
268
foreach ($field->buildFieldIndexes() as $index) {
269
$index->setObjectPHID($phid);
270
$indexes[$index->getTableName()][] = $index;
271
}
272
}
273
274
if (!$indexes) {
275
return;
276
}
277
278
$any_index = head(head($indexes));
279
$conn_w = $any_index->establishConnection('w');
280
281
foreach ($indexes as $table => $index_list) {
282
$sql = array();
283
foreach ($index_list as $index) {
284
$sql[] = $index->formatForInsert($conn_w);
285
}
286
$indexes[$table] = $sql;
287
}
288
289
$any_index->openTransaction();
290
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));
298
299
if (!$sql_list) {
300
continue;
301
}
302
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);
309
}
310
}
311
312
$any_index->saveTransaction();
313
}
314
315
public function updateAbstractDocument(
316
PhabricatorSearchAbstractDocument $document) {
317
318
$role = PhabricatorCustomField::ROLE_GLOBALSEARCH;
319
foreach ($this->getFields() as $field) {
320
if (!$field->shouldEnableForRole($role)) {
321
continue;
322
}
323
$field->updateAbstractDocument($document);
324
}
325
}
326
327
328
}
329
330