@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
at upstream/main 596 lines 15 kB view raw
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}