@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3abstract class PhabricatorStandardCustomField
4 extends PhabricatorCustomField {
5
6 private $rawKey;
7 private $fieldKey;
8 private $fieldName;
9 private $fieldValue;
10 private $fieldDescription;
11 private $fieldConfig;
12 private $applicationField;
13 private $strings = array();
14 private $caption;
15 private $fieldError;
16 private $required;
17 private $default;
18 private $isCopyable;
19 private $hasStorageValue;
20 private $isBuiltin;
21 private $isEnabled = true;
22
23 abstract public function getFieldType();
24
25 public static function buildStandardFields(
26 PhabricatorCustomField $template,
27 array $config,
28 $builtin = false) {
29
30 $types = id(new PhutilClassMapQuery())
31 ->setAncestorClass(self::class)
32 ->setUniqueMethod('getFieldType')
33 ->execute();
34
35 $fields = array();
36 foreach ($config as $key => $value) {
37 $type = idx($value, 'type', 'text');
38 if (empty($types[$type])) {
39 // TODO: We should have better typechecking somewhere, and then make
40 // this more serious.
41 continue;
42 }
43
44 $namespace = $template->getStandardCustomFieldNamespace();
45 $full_key = "std:{$namespace}:{$key}";
46
47 $template = clone $template;
48 $standard = id(clone $types[$type])
49 ->setRawStandardFieldKey($key)
50 ->setFieldKey($full_key)
51 ->setFieldConfig($value)
52 ->setApplicationField($template);
53
54 if ($builtin) {
55 $standard->setIsBuiltin(true);
56 }
57
58 $field = $template->setProxy($standard);
59 $fields[] = $field;
60 }
61
62 return $fields;
63 }
64
65 public function setApplicationField(
66 PhabricatorStandardCustomFieldInterface $application_field) {
67 $this->applicationField = $application_field;
68 return $this;
69 }
70
71 public function getApplicationField() {
72 return $this->applicationField;
73 }
74
75 public function setFieldName($name) {
76 $this->fieldName = $name;
77 return $this;
78 }
79
80 public function getFieldValue() {
81 return $this->fieldValue;
82 }
83
84 public function setFieldValue($value) {
85 $this->fieldValue = $value;
86 return $this;
87 }
88
89 public function setCaption($caption) {
90 $this->caption = $caption;
91 return $this;
92 }
93
94 public function getCaption() {
95 return $this->caption;
96 }
97
98 public function setFieldDescription($description) {
99 $this->fieldDescription = $description;
100 return $this;
101 }
102
103 public function setIsBuiltin($is_builtin) {
104 $this->isBuiltin = $is_builtin;
105 return $this;
106 }
107
108 public function getIsBuiltin() {
109 return $this->isBuiltin;
110 }
111
112 public function setFieldConfig(array $config) {
113 foreach ($config as $key => $value) {
114 switch ($key) {
115 case 'name':
116 $this->setFieldName($value);
117 break;
118 case 'description':
119 $this->setFieldDescription($value);
120 break;
121 case 'strings':
122 $this->setStrings($value);
123 break;
124 case 'caption':
125 $this->setCaption($value);
126 break;
127 case 'required':
128 if ($value) {
129 $this->setRequired($value);
130 $this->setFieldError(true);
131 }
132 break;
133 case 'default':
134 $this->setFieldValue($value);
135 break;
136 case 'copy':
137 $this->setIsCopyable($value);
138 break;
139 case 'type':
140 // We set this earlier on.
141 break;
142 }
143 }
144 $this->fieldConfig = $config;
145 return $this;
146 }
147
148 public function getFieldConfigValue($key, $default = null) {
149 return idx($this->fieldConfig, $key, $default);
150 }
151
152 public function setFieldError($field_error) {
153 $this->fieldError = $field_error;
154 return $this;
155 }
156
157 public function getFieldError() {
158 return $this->fieldError;
159 }
160
161 public function setRequired($required) {
162 $this->required = $required;
163 return $this;
164 }
165
166 public function getRequired() {
167 return $this->required;
168 }
169
170 public function setRawStandardFieldKey($raw_key) {
171 $this->rawKey = $raw_key;
172 return $this;
173 }
174
175 public function getRawStandardFieldKey() {
176 return $this->rawKey;
177 }
178
179 public function setIsEnabled($is_enabled) {
180 $this->isEnabled = $is_enabled;
181 return $this;
182 }
183
184 public function getIsEnabled() {
185 return $this->isEnabled;
186 }
187
188 public function isFieldEnabled() {
189 return $this->getIsEnabled();
190 }
191
192
193/* -( PhabricatorCustomField )--------------------------------------------- */
194
195
196 public function setFieldKey($field_key) {
197 $this->fieldKey = $field_key;
198 return $this;
199 }
200
201 public function getFieldKey() {
202 return $this->fieldKey;
203 }
204
205 public function getFieldName() {
206 return coalesce($this->fieldName, parent::getFieldName());
207 }
208
209 public function getFieldDescription() {
210 return coalesce($this->fieldDescription, parent::getFieldDescription());
211 }
212
213 public function setStrings(array $strings) {
214 $this->strings = $strings;
215 return;
216 }
217
218 public function getString($key, $default = null) {
219 return idx($this->strings, $key, $default);
220 }
221
222 public function setIsCopyable($is_copyable) {
223 $this->isCopyable = $is_copyable;
224 return $this;
225 }
226
227 public function getIsCopyable() {
228 return $this->isCopyable;
229 }
230
231 public function shouldUseStorage() {
232 try {
233 $object = $this->newStorageObject();
234 return true;
235 } catch (PhabricatorCustomFieldImplementationIncompleteException $ex) {
236 return false;
237 }
238 }
239
240 public function getValueForStorage() {
241 return $this->getFieldValue();
242 }
243
244 public function setValueFromStorage($value) {
245 return $this->setFieldValue($value);
246 }
247
248 public function didSetValueFromStorage() {
249 $this->hasStorageValue = true;
250 return $this;
251 }
252
253 public function getOldValueForApplicationTransactions() {
254 if ($this->hasStorageValue) {
255 return $this->getValueForStorage();
256 } else {
257 return null;
258 }
259 }
260
261 public function shouldAppearInApplicationTransactions() {
262 return true;
263 }
264
265 public function shouldAppearInEditView() {
266 return $this->getFieldConfigValue('edit', true);
267 }
268
269 public function readValueFromRequest(AphrontRequest $request) {
270 $value = $request->getStr($this->getFieldKey());
271 if (!phutil_nonempty_string($value)) {
272 $value = null;
273 }
274 $this->setFieldValue($value);
275 }
276
277 public function getInstructionsForEdit() {
278 return $this->getFieldConfigValue('instructions');
279 }
280
281 public function getPlaceholder() {
282 return $this->getFieldConfigValue('placeholder', null);
283 }
284
285 public function renderEditControl(array $handles) {
286 return id(new AphrontFormTextControl())
287 ->setName($this->getFieldKey())
288 ->setCaption($this->getCaption())
289 ->setValue($this->getFieldValue())
290 ->setError($this->getFieldError())
291 ->setLabel($this->getFieldName())
292 ->setPlaceholder($this->getPlaceholder());
293 }
294
295 public function newStorageObject() {
296 return $this->getApplicationField()->newStorageObject();
297 }
298
299 public function shouldAppearInPropertyView() {
300 return $this->getFieldConfigValue('view', true);
301 }
302
303 public function renderPropertyViewValue(array $handles) {
304 return $this->renderValue();
305 }
306
307 protected function renderValue() {
308 // If your field needs to render anything more complicated then a string,
309 // then you should override this method.
310 $value_str = phutil_string_cast($this->getFieldValue());
311
312 if (phutil_nonempty_string($value_str)) {
313 return $value_str;
314 }
315 return null;
316 }
317
318 public function shouldAppearInListView() {
319 return $this->getFieldConfigValue('list', false);
320 }
321
322 public function getStyleForListItemView() {
323 return $this->getFieldConfigValue('list');
324 }
325
326 public function renderListItemValue() {
327 return $this->renderValue();
328 }
329
330 private function isValue($something) {
331 if (is_object($something)) {
332 return true;
333 }
334 return phutil_nonempty_scalar($something);
335 }
336
337 public function getValueForListItem() {
338 $style = $this->getStyleForListItemView();
339 $value = $this->renderListItemValue();
340 if (!$this->isValue($value) || !$style) {
341 return null;
342 }
343 switch ($style) {
344 case 'icon':
345 // maybe expose 'list.icon.alt' for hover stuff?
346 // also icon's "label", and other features supported by
347 // PHUIObjectItemView::addIcon().
348 return 'fa-'.$this->getFieldConfigValue('list.icon');
349 case 'attribute':
350 case 'byline':
351 $label = $this->getFieldConfigValue(
352 'list.label',
353 $this->getFieldName());
354 if (phutil_nonempty_string($label)) {
355 return pht('%s: %s', $label, $value);
356 }
357 return $value;
358 default:
359 throw new Exception(
360 pht(
361 "Unknown field list-item view style '%s'; valid styles are ".
362 "'%s', '%s'and '%s'.",
363 $style,
364 'icon',
365 'attribute',
366 'byline'));
367 }
368 }
369
370 public function renderOnListItem(PHUIObjectItemView $view) {
371 $value = $this->getValueForListItem();
372 if (!$this->isValue($value)) {
373 return;
374 }
375
376 switch ($this->getStyleForListItemView()) {
377 case 'icon':
378 $view->addIcon($value);
379 break;
380 case 'attribute':
381 $view->addAttribute($value);
382 break;
383 case 'byline':
384 $view->addByline($value);
385 break;
386 }
387 }
388
389 public function shouldAppearInApplicationSearch() {
390 return $this->getFieldConfigValue('search', false);
391 }
392
393 protected function newStringIndexStorage() {
394 return $this->getApplicationField()->newStringIndexStorage();
395 }
396
397 protected function newNumericIndexStorage() {
398 return $this->getApplicationField()->newNumericIndexStorage();
399 }
400
401 public function buildFieldIndexes() {
402 return array();
403 }
404
405 public function buildOrderIndex() {
406 return null;
407 }
408
409 public function readApplicationSearchValueFromRequest(
410 PhabricatorApplicationSearchEngine $engine,
411 AphrontRequest $request) {
412 return;
413 }
414
415 public function applyApplicationSearchConstraintToQuery(
416 PhabricatorApplicationSearchEngine $engine,
417 PhabricatorCursorPagedPolicyAwareQuery $query,
418 $value) {
419 return;
420 }
421
422 public function appendToApplicationSearchForm(
423 PhabricatorApplicationSearchEngine $engine,
424 AphrontFormView $form,
425 $value) {
426 return;
427 }
428
429 public function validateApplicationTransactions(
430 PhabricatorApplicationTransactionEditor $editor,
431 $type,
432 array $xactions) {
433
434 $this->setFieldError(null);
435
436 $errors = parent::validateApplicationTransactions(
437 $editor,
438 $type,
439 $xactions);
440
441 if ($this->getRequired()) {
442 $value = $this->getOldValueForApplicationTransactions();
443
444 $transaction = null;
445 foreach ($xactions as $xaction) {
446 $value = $xaction->getNewValue();
447 if (!$this->isValueEmpty($value)) {
448 $transaction = $xaction;
449 break;
450 }
451 }
452 if ($this->isValueEmpty($value)) {
453 $error = new PhabricatorApplicationTransactionValidationError(
454 $type,
455 pht('Required'),
456 pht('%s is required.', $this->getFieldName()),
457 $transaction);
458 $error->setIsMissingFieldError(true);
459 $errors[] = $error;
460 $this->setFieldError(pht('Required'));
461 }
462 }
463
464 return $errors;
465 }
466
467 protected function isValueEmpty($value) {
468 if (is_array($value)) {
469 return empty($value);
470 }
471 return $value === null || !strlen($value);
472 }
473
474 public function getApplicationTransactionTitle(
475 PhabricatorApplicationTransaction $xaction) {
476 $author_phid = $xaction->getAuthorPHID();
477 $old = $xaction->getOldValue();
478 $new = $xaction->getNewValue();
479
480 if (!$old) {
481 return pht(
482 '%s set %s to %s.',
483 $xaction->renderHandleLink($author_phid),
484 $this->getFieldName(),
485 $new);
486 } else if (!$new) {
487 return pht(
488 '%s removed %s.',
489 $xaction->renderHandleLink($author_phid),
490 $this->getFieldName());
491 } else {
492 return pht(
493 '%s changed %s from %s to %s.',
494 $xaction->renderHandleLink($author_phid),
495 $this->getFieldName(),
496 $old,
497 $new);
498 }
499 }
500
501 public function getApplicationTransactionTitleForFeed(
502 PhabricatorApplicationTransaction $xaction) {
503
504 $author_phid = $xaction->getAuthorPHID();
505 $object_phid = $xaction->getObjectPHID();
506
507 $old = $xaction->getOldValue();
508 $new = $xaction->getNewValue();
509
510 if (!$old) {
511 return pht(
512 '%s set %s to %s on %s.',
513 $xaction->renderHandleLink($author_phid),
514 $this->getFieldName(),
515 $new,
516 $xaction->renderHandleLink($object_phid));
517 } else if (!$new) {
518 return pht(
519 '%s removed %s on %s.',
520 $xaction->renderHandleLink($author_phid),
521 $this->getFieldName(),
522 $xaction->renderHandleLink($object_phid));
523 } else {
524 return pht(
525 '%s changed %s from %s to %s on %s.',
526 $xaction->renderHandleLink($author_phid),
527 $this->getFieldName(),
528 $old,
529 $new,
530 $xaction->renderHandleLink($object_phid));
531 }
532 }
533
534 public function getHeraldFieldValue() {
535 return $this->getFieldValue();
536 }
537
538 public function getFieldControlID($key = null) {
539 $key = coalesce($key, $this->getRawStandardFieldKey());
540 return 'std:control:'.$key;
541 }
542
543 public function shouldAppearInGlobalSearch() {
544 return $this->getFieldConfigValue('fulltext', false);
545 }
546
547 public function updateAbstractDocument(
548 PhabricatorSearchAbstractDocument $document) {
549
550 $field_key = $this->getFieldConfigValue('fulltext');
551
552 // If the caller or configuration didn't specify a valid field key,
553 // generate one automatically from the field index.
554 if (!is_string($field_key) || (strlen($field_key) != 4)) {
555 $field_key = '!'.substr($this->getFieldIndex(), 0, 3);
556 }
557
558 $field_value = $this->getFieldValue();
559 if (($field_value !== null) && (strlen($field_value))) {
560 $document->addField($field_key, $field_value);
561 }
562 }
563
564 protected function newStandardEditField() {
565 $short = $this->getModernFieldKey();
566
567 return parent::newStandardEditField()
568 ->setEditTypeKey($short)
569 ->setIsCopyable($this->getIsCopyable());
570 }
571
572 public function shouldAppearInConduitTransactions() {
573 return true;
574 }
575
576 public function shouldAppearInConduitDictionary() {
577 return true;
578 }
579
580 public function getModernFieldKey() {
581 if ($this->getIsBuiltin()) {
582 return $this->getRawStandardFieldKey();
583 } else {
584 return 'custom.'.$this->getRawStandardFieldKey();
585 }
586 }
587
588 public function getConduitDictionaryValue() {
589 return $this->getFieldValue();
590 }
591
592 public function newExportData() {
593 return $this->getFieldValue();
594 }
595
596}