[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @task apps Building Applications with Custom Fields 5 * @task core Core Properties and Field Identity 6 * @task proxy Field Proxies 7 * @task context Contextual Data 8 * @task render Rendering Utilities 9 * @task storage Field Storage 10 * @task edit Integration with Edit Views 11 * @task view Integration with Property Views 12 * @task list Integration with List views 13 * @task appsearch Integration with ApplicationSearch 14 * @task appxaction Integration with ApplicationTransactions 15 * @task xactionmail Integration with Transaction Mail 16 * @task globalsearch Integration with Global Search 17 * @task herald Integration with Herald 18 */ 19 abstract class PhabricatorCustomField { 20 21 private $viewer; 22 private $object; 23 private $proxy; 24 25 const ROLE_APPLICATIONTRANSACTIONS = 'ApplicationTransactions'; 26 const ROLE_TRANSACTIONMAIL = 'ApplicationTransactions.mail'; 27 const ROLE_APPLICATIONSEARCH = 'ApplicationSearch'; 28 const ROLE_STORAGE = 'storage'; 29 const ROLE_DEFAULT = 'default'; 30 const ROLE_EDIT = 'edit'; 31 const ROLE_VIEW = 'view'; 32 const ROLE_LIST = 'list'; 33 const ROLE_GLOBALSEARCH = 'GlobalSearch'; 34 const ROLE_CONDUIT = 'conduit'; 35 const ROLE_HERALD = 'herald'; 36 37 38 /* -( Building Applications with Custom Fields )--------------------------- */ 39 40 41 /** 42 * @task apps 43 */ 44 public static function getObjectFields( 45 PhabricatorCustomFieldInterface $object, 46 $role) { 47 48 try { 49 $attachment = $object->getCustomFields(); 50 } catch (PhabricatorDataNotAttachedException $ex) { 51 $attachment = new PhabricatorCustomFieldAttachment(); 52 $object->attachCustomFields($attachment); 53 } 54 55 try { 56 $field_list = $attachment->getCustomFieldList($role); 57 } catch (PhabricatorCustomFieldNotAttachedException $ex) { 58 $base_class = $object->getCustomFieldBaseClass(); 59 60 $spec = $object->getCustomFieldSpecificationForRole($role); 61 if (!is_array($spec)) { 62 $obj_class = get_class($object); 63 throw new Exception( 64 "Expected an array from getCustomFieldSpecificationForRole() for ". 65 "object of class '{$obj_class}'."); 66 } 67 68 $fields = PhabricatorCustomField::buildFieldList( 69 $base_class, 70 $spec, 71 $object); 72 73 foreach ($fields as $key => $field) { 74 if (!$field->shouldEnableForRole($role)) { 75 unset($fields[$key]); 76 } 77 } 78 79 foreach ($fields as $field) { 80 $field->setObject($object); 81 } 82 83 $field_list = new PhabricatorCustomFieldList($fields); 84 $attachment->addCustomFieldList($role, $field_list); 85 } 86 87 return $field_list; 88 } 89 90 91 /** 92 * @task apps 93 */ 94 public static function getObjectField( 95 PhabricatorCustomFieldInterface $object, 96 $role, 97 $field_key) { 98 99 $fields = self::getObjectFields($object, $role)->getFields(); 100 101 return idx($fields, $field_key); 102 } 103 104 105 /** 106 * @task apps 107 */ 108 public static function buildFieldList($base_class, array $spec, $object) { 109 $field_objects = id(new PhutilSymbolLoader()) 110 ->setAncestorClass($base_class) 111 ->loadObjects(); 112 113 $fields = array(); 114 $from_map = array(); 115 foreach ($field_objects as $field_object) { 116 $current_class = get_class($field_object); 117 foreach ($field_object->createFields($object) as $field) { 118 $key = $field->getFieldKey(); 119 if (isset($fields[$key])) { 120 $original_class = $from_map[$key]; 121 throw new Exception( 122 "Both '{$original_class}' and '{$current_class}' define a custom ". 123 "field with field key '{$key}'. Field keys must be unique."); 124 } 125 $from_map[$key] = $current_class; 126 $fields[$key] = $field; 127 } 128 } 129 130 foreach ($fields as $key => $field) { 131 if (!$field->isFieldEnabled()) { 132 unset($fields[$key]); 133 } 134 } 135 136 $fields = array_select_keys($fields, array_keys($spec)) + $fields; 137 138 foreach ($spec as $key => $config) { 139 if (empty($fields[$key])) { 140 continue; 141 } 142 if (!empty($config['disabled'])) { 143 if ($fields[$key]->canDisableField()) { 144 unset($fields[$key]); 145 } 146 } 147 } 148 149 return $fields; 150 } 151 152 153 /* -( Core Properties and Field Identity )--------------------------------- */ 154 155 156 /** 157 * Return a key which uniquely identifies this field, like 158 * "mycompany:dinosaur:count". Normally you should provide some level of 159 * namespacing to prevent collisions. 160 * 161 * @return string String which uniquely identifies this field. 162 * @task core 163 */ 164 public function getFieldKey() { 165 if ($this->proxy) { 166 return $this->proxy->getFieldKey(); 167 } 168 throw new PhabricatorCustomFieldImplementationIncompleteException( 169 $this, 170 $field_key_is_incomplete = true); 171 } 172 173 174 /** 175 * Return a human-readable field name. 176 * 177 * @return string Human readable field name. 178 * @task core 179 */ 180 public function getFieldName() { 181 if ($this->proxy) { 182 return $this->proxy->getFieldName(); 183 } 184 return $this->getFieldKey(); 185 } 186 187 188 /** 189 * Return a short, human-readable description of the field's behavior. This 190 * provides more context to administrators when they are customizing fields. 191 * 192 * @return string|null Optional human-readable description. 193 * @task core 194 */ 195 public function getFieldDescription() { 196 if ($this->proxy) { 197 return $this->proxy->getFieldDescription(); 198 } 199 return null; 200 } 201 202 203 /** 204 * Most field implementations are unique, in that one class corresponds to 205 * one field. However, some field implementations are general and a single 206 * implementation may drive several fields. 207 * 208 * For general implementations, the general field implementation can return 209 * multiple field instances here. 210 * 211 * @param object The object to create fields for. 212 * @return list<PhabricatorCustomField> List of fields. 213 * @task core 214 */ 215 public function createFields($object) { 216 return array($this); 217 } 218 219 220 /** 221 * You can return `false` here if the field should not be enabled for any 222 * role. For example, it might depend on something (like an application or 223 * library) which isn't installed, or might have some global configuration 224 * which allows it to be disabled. 225 * 226 * @return bool False to completely disable this field for all roles. 227 * @task core 228 */ 229 public function isFieldEnabled() { 230 if ($this->proxy) { 231 return $this->proxy->isFieldEnabled(); 232 } 233 return true; 234 } 235 236 237 /** 238 * Low level selector for field availability. Fields can appear in different 239 * roles (like an edit view, a list view, etc.), but not every field needs 240 * to appear everywhere. Fields that are disabled in a role won't appear in 241 * that context within applications. 242 * 243 * Normally, you do not need to override this method. Instead, override the 244 * methods specific to roles you want to enable. For example, implement 245 * @{method:shouldUseStorage()} to activate the `'storage'` role. 246 * 247 * @return bool True to enable the field for the given role. 248 * @task core 249 */ 250 public function shouldEnableForRole($role) { 251 252 // NOTE: All of these calls proxy individually, so we don't need to 253 // proxy this call as a whole. 254 255 switch ($role) { 256 case self::ROLE_APPLICATIONTRANSACTIONS: 257 return $this->shouldAppearInApplicationTransactions(); 258 case self::ROLE_APPLICATIONSEARCH: 259 return $this->shouldAppearInApplicationSearch(); 260 case self::ROLE_STORAGE: 261 return $this->shouldUseStorage(); 262 case self::ROLE_EDIT: 263 return $this->shouldAppearInEditView(); 264 case self::ROLE_VIEW: 265 return $this->shouldAppearInPropertyView(); 266 case self::ROLE_LIST: 267 return $this->shouldAppearInListView(); 268 case self::ROLE_GLOBALSEARCH: 269 return $this->shouldAppearInGlobalSearch(); 270 case self::ROLE_CONDUIT: 271 return $this->shouldAppearInConduitDictionary(); 272 case self::ROLE_TRANSACTIONMAIL: 273 return $this->shouldAppearInTransactionMail(); 274 case self::ROLE_HERALD: 275 return $this->shouldAppearInHerald(); 276 case self::ROLE_DEFAULT: 277 return true; 278 default: 279 throw new Exception("Unknown field role '{$role}'!"); 280 } 281 } 282 283 284 /** 285 * Allow administrators to disable this field. Most fields should allow this, 286 * but some are fundamental to the behavior of the application and can be 287 * locked down to avoid chaos, disorder, and the decline of civilization. 288 * 289 * @return bool False to prevent this field from being disabled through 290 * configuration. 291 * @task core 292 */ 293 public function canDisableField() { 294 return true; 295 } 296 297 public function shouldDisableByDefault() { 298 return false; 299 } 300 301 302 /** 303 * Return an index string which uniquely identifies this field. 304 * 305 * @return string Index string which uniquely identifies this field. 306 * @task core 307 */ 308 final public function getFieldIndex() { 309 return PhabricatorHash::digestForIndex($this->getFieldKey()); 310 } 311 312 313 /* -( Field Proxies )------------------------------------------------------ */ 314 315 316 /** 317 * Proxies allow a field to use some other field's implementation for most 318 * of their behavior while still subclassing an application field. When a 319 * proxy is set for a field with @{method:setProxy}, all of its methods will 320 * call through to the proxy by default. 321 * 322 * This is most commonly used to implement configuration-driven custom fields 323 * using @{class:PhabricatorStandardCustomField}. 324 * 325 * This method must be overridden to return `true` before a field can accept 326 * proxies. 327 * 328 * @return bool True if you can @{method:setProxy} this field. 329 * @task proxy 330 */ 331 public function canSetProxy() { 332 if ($this instanceof PhabricatorStandardCustomFieldInterface) { 333 return true; 334 } 335 return false; 336 } 337 338 339 /** 340 * Set the proxy implementation for this field. See @{method:canSetProxy} for 341 * discussion of field proxies. 342 * 343 * @param PhabricatorCustomField Field implementation. 344 * @return this 345 */ 346 final public function setProxy(PhabricatorCustomField $proxy) { 347 if (!$this->canSetProxy()) { 348 throw new PhabricatorCustomFieldNotProxyException($this); 349 } 350 351 $this->proxy = $proxy; 352 return $this; 353 } 354 355 356 /** 357 * Get the field's proxy implementation, if any. For discussion, see 358 * @{method:canSetProxy}. 359 * 360 * @return PhabricatorCustomField|null Proxy field, if one is set. 361 */ 362 final public function getProxy() { 363 return $this->proxy; 364 } 365 366 367 /* -( Contextual Data )---------------------------------------------------- */ 368 369 370 /** 371 * Sets the object this field belongs to. 372 * 373 * @param PhabricatorCustomFieldInterface The object this field belongs to. 374 * @return this 375 * @task context 376 */ 377 final public function setObject(PhabricatorCustomFieldInterface $object) { 378 if ($this->proxy) { 379 $this->proxy->setObject($object); 380 return $this; 381 } 382 383 $this->object = $object; 384 $this->didSetObject($object); 385 return $this; 386 } 387 388 389 /** 390 * Read object data into local field storage, if applicable. 391 * 392 * @param PhabricatorCustomFieldInterface The object this field belongs to. 393 * @return this 394 * @task context 395 */ 396 public function readValueFromObject(PhabricatorCustomFieldInterface $object) { 397 if ($this->proxy) { 398 $this->proxy->readValueFromObject($object); 399 } 400 return $this; 401 } 402 403 404 /** 405 * Get the object this field belongs to. 406 * 407 * @return PhabricatorCustomFieldInterface The object this field belongs to. 408 * @task context 409 */ 410 final public function getObject() { 411 if ($this->proxy) { 412 return $this->proxy->getObject(); 413 } 414 415 return $this->object; 416 } 417 418 419 /** 420 * This is a hook, primarily for subclasses to load object data. 421 * 422 * @return PhabricatorCustomFieldInterface The object this field belongs to. 423 * @return void 424 */ 425 protected function didSetObject(PhabricatorCustomFieldInterface $object) { 426 return; 427 } 428 429 430 /** 431 * @task context 432 */ 433 final public function setViewer(PhabricatorUser $viewer) { 434 if ($this->proxy) { 435 $this->proxy->setViewer($viewer); 436 return $this; 437 } 438 439 $this->viewer = $viewer; 440 return $this; 441 } 442 443 444 /** 445 * @task context 446 */ 447 final public function getViewer() { 448 if ($this->proxy) { 449 return $this->proxy->getViewer(); 450 } 451 452 return $this->viewer; 453 } 454 455 456 /** 457 * @task context 458 */ 459 final protected function requireViewer() { 460 if ($this->proxy) { 461 return $this->proxy->requireViewer(); 462 } 463 464 if (!$this->viewer) { 465 throw new PhabricatorCustomFieldDataNotAvailableException($this); 466 } 467 return $this->viewer; 468 } 469 470 471 /* -( Rendering Utilities )------------------------------------------------ */ 472 473 474 /** 475 * @task render 476 */ 477 protected function renderHandleList(array $handles) { 478 if (!$handles) { 479 return null; 480 } 481 482 $out = array(); 483 foreach ($handles as $handle) { 484 $out[] = $handle->renderLink(); 485 } 486 487 return phutil_implode_html(phutil_tag('br'), $out); 488 } 489 490 491 /* -( Storage )------------------------------------------------------------ */ 492 493 494 /** 495 * Return true to use field storage. 496 * 497 * Fields which can be edited by the user will most commonly use storage, 498 * while some other types of fields (for instance, those which just display 499 * information in some stylized way) may not. Many builtin fields do not use 500 * storage because their data is available on the object itself. 501 * 502 * If you implement this, you must also implement @{method:getValueForStorage} 503 * and @{method:setValueFromStorage}. 504 * 505 * @return bool True to use storage. 506 * @task storage 507 */ 508 public function shouldUseStorage() { 509 if ($this->proxy) { 510 return $this->proxy->shouldUseStorage(); 511 } 512 return false; 513 } 514 515 516 /** 517 * Return a new, empty storage object. This should be a subclass of 518 * @{class:PhabricatorCustomFieldStorage} which is bound to the application's 519 * database. 520 * 521 * @return PhabricatorCustomFieldStorage New empty storage object. 522 * @task storage 523 */ 524 public function newStorageObject() { 525 if ($this->proxy) { 526 return $this->proxy->newStorageObject(); 527 } 528 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 529 } 530 531 532 /** 533 * Return a serialized representation of the field value, appropriate for 534 * storing in auxiliary field storage. You must implement this method if 535 * you implement @{method:shouldUseStorage}. 536 * 537 * If the field value is a scalar, it can be returned unmodiifed. If not, 538 * it should be serialized (for example, using JSON). 539 * 540 * @return string Serialized field value. 541 * @task storage 542 */ 543 public function getValueForStorage() { 544 if ($this->proxy) { 545 return $this->proxy->getValueForStorage(); 546 } 547 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 548 } 549 550 551 /** 552 * Set the field's value given a serialized storage value. This is called 553 * when the field is loaded; if no data is available, the value will be 554 * null. You must implement this method if you implement 555 * @{method:shouldUseStorage}. 556 * 557 * Usually, the value can be loaded directly. If it isn't a scalar, you'll 558 * need to undo whatever serialization you applied in 559 * @{method:getValueForStorage}. 560 * 561 * @param string|null Serialized field representation (from 562 * @{method:getValueForStorage}) or null if no value has 563 * ever been stored. 564 * @return this 565 * @task storage 566 */ 567 public function setValueFromStorage($value) { 568 if ($this->proxy) { 569 return $this->proxy->setValueFromStorage($value); 570 } 571 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 572 } 573 574 575 /* -( ApplicationSearch )-------------------------------------------------- */ 576 577 578 /** 579 * Appearing in ApplicationSearch allows a field to be indexed and searched 580 * for. 581 * 582 * @return bool True to appear in ApplicationSearch. 583 * @task appsearch 584 */ 585 public function shouldAppearInApplicationSearch() { 586 if ($this->proxy) { 587 return $this->proxy->shouldAppearInApplicationSearch(); 588 } 589 return false; 590 } 591 592 593 /** 594 * Return one or more indexes which this field can meaningfully query against 595 * to implement ApplicationSearch. 596 * 597 * Normally, you should build these using @{method:newStringIndex} and 598 * @{method:newNumericIndex}. For example, if a field holds a numeric value 599 * it might return a single numeric index: 600 * 601 * return array($this->newNumericIndex($this->getValue())); 602 * 603 * If a field holds a more complex value (like a list of users), it might 604 * return several string indexes: 605 * 606 * $indexes = array(); 607 * foreach ($this->getValue() as $phid) { 608 * $indexes[] = $this->newStringIndex($phid); 609 * } 610 * return $indexes; 611 * 612 * @return list<PhabricatorCustomFieldIndexStorage> List of indexes. 613 * @task appsearch 614 */ 615 public function buildFieldIndexes() { 616 if ($this->proxy) { 617 return $this->proxy->buildFieldIndexes(); 618 } 619 return array(); 620 } 621 622 623 /** 624 * Return an index against which this field can be meaningfully ordered 625 * against to implement ApplicationSearch. 626 * 627 * This should be a single index, normally built using 628 * @{method:newStringIndex} and @{method:newNumericIndex}. 629 * 630 * The value of the index is not used. 631 * 632 * Return null from this method if the field can not be ordered. 633 * 634 * @return PhabricatorCustomFieldIndexStorage A single index to order by. 635 * @task appsearch 636 */ 637 public function buildOrderIndex() { 638 if ($this->proxy) { 639 return $this->proxy->buildOrderIndex(); 640 } 641 return null; 642 } 643 644 645 /** 646 * Build a new empty storage object for storing string indexes. Normally, 647 * this should be a concrete subclass of 648 * @{class:PhabricatorCustomFieldStringIndexStorage}. 649 * 650 * @return PhabricatorCustomFieldStringIndexStorage Storage object. 651 * @task appsearch 652 */ 653 protected function newStringIndexStorage() { 654 // NOTE: This intentionally isn't proxied, to avoid call cycles. 655 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 656 } 657 658 659 /** 660 * Build a new empty storage object for storing string indexes. Normally, 661 * this should be a concrete subclass of 662 * @{class:PhabricatorCustomFieldStringIndexStorage}. 663 * 664 * @return PhabricatorCustomFieldStringIndexStorage Storage object. 665 * @task appsearch 666 */ 667 protected function newNumericIndexStorage() { 668 // NOTE: This intentionally isn't proxied, to avoid call cycles. 669 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 670 } 671 672 673 /** 674 * Build and populate storage for a string index. 675 * 676 * @param string String to index. 677 * @return PhabricatorCustomFieldStringIndexStorage Populated storage. 678 * @task appsearch 679 */ 680 protected function newStringIndex($value) { 681 if ($this->proxy) { 682 return $this->proxy->newStringIndex(); 683 } 684 685 $key = $this->getFieldIndex(); 686 return $this->newStringIndexStorage() 687 ->setIndexKey($key) 688 ->setIndexValue($value); 689 } 690 691 692 /** 693 * Build and populate storage for a numeric index. 694 * 695 * @param string Numeric value to index. 696 * @return PhabricatorCustomFieldNumericIndexStorage Populated storage. 697 * @task appsearch 698 */ 699 protected function newNumericIndex($value) { 700 if ($this->proxy) { 701 return $this->proxy->newNumericIndex(); 702 } 703 $key = $this->getFieldIndex(); 704 return $this->newNumericIndexStorage() 705 ->setIndexKey($key) 706 ->setIndexValue($value); 707 } 708 709 710 /** 711 * Read a query value from a request, for storage in a saved query. Normally, 712 * this method should, e.g., read a string out of the request. 713 * 714 * @param PhabricatorApplicationSearchEngine Engine building the query. 715 * @param AphrontRequest Request to read from. 716 * @return wild 717 * @task appsearch 718 */ 719 public function readApplicationSearchValueFromRequest( 720 PhabricatorApplicationSearchEngine $engine, 721 AphrontRequest $request) { 722 if ($this->proxy) { 723 return $this->proxy->readApplicationSearchValueFromRequest( 724 $engine, 725 $request); 726 } 727 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 728 } 729 730 731 /** 732 * Constrain a query, given a field value. Generally, this method should 733 * use `with...()` methods to apply filters or other constraints to the 734 * query. 735 * 736 * @param PhabricatorApplicationSearchEngine Engine executing the query. 737 * @param PhabricatorCursorPagedPolicyAwareQuery Query to constrain. 738 * @param wild Constraint provided by the user. 739 * @return void 740 * @task appsearch 741 */ 742 public function applyApplicationSearchConstraintToQuery( 743 PhabricatorApplicationSearchEngine $engine, 744 PhabricatorCursorPagedPolicyAwareQuery $query, 745 $value) { 746 if ($this->proxy) { 747 return $this->proxy->applyApplicationSearchConstraintToQuery( 748 $engine, 749 $query, 750 $value); 751 } 752 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 753 } 754 755 756 /** 757 * Append search controls to the interface. If you need handles, use 758 * @{method:getRequiredHandlePHIDsForApplicationSearch} to get them. 759 * 760 * @param PhabricatorApplicationSearchEngine Engine constructing the form. 761 * @param AphrontFormView The form to update. 762 * @param wild Value from the saved query. 763 * @param list<PhabricatorObjectHandle> List of handles. 764 * @return void 765 * @task appsearch 766 */ 767 public function appendToApplicationSearchForm( 768 PhabricatorApplicationSearchEngine $engine, 769 AphrontFormView $form, 770 $value, 771 array $handles) { 772 if ($this->proxy) { 773 return $this->proxy->appendToApplicationSearchForm( 774 $engine, 775 $form, 776 $value, 777 $handles); 778 } 779 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 780 } 781 782 783 /** 784 * Return a list of PHIDs which @{method:appendToApplicationSearchForm} needs 785 * handles for. This is primarily useful if the field stores PHIDs and you 786 * need to (for example) render a tokenizer control. 787 * 788 * @param wild Value from the saved query. 789 * @return list<phid> List of PHIDs. 790 * @task appsearch 791 */ 792 public function getRequiredHandlePHIDsForApplicationSearch($value) { 793 if ($this->proxy) { 794 return $this->proxy->getRequiredHandlePHIDsForApplicationSearch($value); 795 } 796 return array(); 797 } 798 799 800 /* -( ApplicationTransactions )-------------------------------------------- */ 801 802 803 /** 804 * Appearing in ApplicationTrasactions allows a field to be edited using 805 * standard workflows. 806 * 807 * @return bool True to appear in ApplicationTransactions. 808 * @task appxaction 809 */ 810 public function shouldAppearInApplicationTransactions() { 811 if ($this->proxy) { 812 return $this->proxy->shouldAppearInApplicationTransactions(); 813 } 814 return false; 815 } 816 817 818 /** 819 * @task appxaction 820 */ 821 public function getApplicationTransactionType() { 822 if ($this->proxy) { 823 return $this->proxy->getApplicationTransactionType(); 824 } 825 return PhabricatorTransactions::TYPE_CUSTOMFIELD; 826 } 827 828 829 /** 830 * @task appxaction 831 */ 832 public function getApplicationTransactionMetadata() { 833 if ($this->proxy) { 834 return $this->proxy->getApplicationTransactionMetadata(); 835 } 836 return array(); 837 } 838 839 840 /** 841 * @task appxaction 842 */ 843 public function getOldValueForApplicationTransactions() { 844 if ($this->proxy) { 845 return $this->proxy->getOldValueForApplicationTransactions(); 846 } 847 return $this->getValueForStorage(); 848 } 849 850 851 /** 852 * @task appxaction 853 */ 854 public function getNewValueForApplicationTransactions() { 855 if ($this->proxy) { 856 return $this->proxy->getNewValueForApplicationTransactions(); 857 } 858 return $this->getValueForStorage(); 859 } 860 861 862 /** 863 * @task appxaction 864 */ 865 public function setValueFromApplicationTransactions($value) { 866 if ($this->proxy) { 867 return $this->proxy->setValueFromApplicationTransactions($value); 868 } 869 return $this->setValueFromStorage($value); 870 } 871 872 873 /** 874 * @task appxaction 875 */ 876 public function getNewValueFromApplicationTransactions( 877 PhabricatorApplicationTransaction $xaction) { 878 if ($this->proxy) { 879 return $this->proxy->getNewValueFromApplicationTransactions($xaction); 880 } 881 return $xaction->getNewValue(); 882 } 883 884 885 /** 886 * @task appxaction 887 */ 888 public function getApplicationTransactionHasEffect( 889 PhabricatorApplicationTransaction $xaction) { 890 if ($this->proxy) { 891 return $this->proxy->getApplicationTransactionHasEffect($xaction); 892 } 893 return ($xaction->getOldValue() !== $xaction->getNewValue()); 894 } 895 896 897 /** 898 * @task appxaction 899 */ 900 public function applyApplicationTransactionInternalEffects( 901 PhabricatorApplicationTransaction $xaction) { 902 if ($this->proxy) { 903 return $this->proxy->applyApplicationTransactionInternalEffects($xaction); 904 } 905 return; 906 } 907 908 909 /** 910 * @task appxaction 911 */ 912 public function getApplicationTransactionRemarkupBlocks( 913 PhabricatorApplicationTransaction $xaction) { 914 if ($this->proxy) { 915 return $this->proxy->getApplicationTransactionRemarkupBlocks($xaction); 916 } 917 return array(); 918 } 919 920 921 /** 922 * @task appxaction 923 */ 924 public function applyApplicationTransactionExternalEffects( 925 PhabricatorApplicationTransaction $xaction) { 926 if ($this->proxy) { 927 return $this->proxy->applyApplicationTransactionExternalEffects($xaction); 928 } 929 930 if (!$this->shouldEnableForRole(self::ROLE_STORAGE)) { 931 return; 932 } 933 934 $this->setValueFromApplicationTransactions($xaction->getNewValue()); 935 $value = $this->getValueForStorage(); 936 937 $table = $this->newStorageObject(); 938 $conn_w = $table->establishConnection('w'); 939 940 if ($value === null) { 941 queryfx( 942 $conn_w, 943 'DELETE FROM %T WHERE objectPHID = %s AND fieldIndex = %s', 944 $table->getTableName(), 945 $this->getObject()->getPHID(), 946 $this->getFieldIndex()); 947 } else { 948 queryfx( 949 $conn_w, 950 'INSERT INTO %T (objectPHID, fieldIndex, fieldValue) 951 VALUES (%s, %s, %s) 952 ON DUPLICATE KEY UPDATE fieldValue = VALUES(fieldValue)', 953 $table->getTableName(), 954 $this->getObject()->getPHID(), 955 $this->getFieldIndex(), 956 $value); 957 } 958 959 return; 960 } 961 962 963 /** 964 * Validate transactions for an object. This allows you to raise an error 965 * when a transaction would set a field to an invalid value, or when a field 966 * is required but no transactions provide value. 967 * 968 * @param PhabricatorLiskDAO Editor applying the transactions. 969 * @param string Transaction type. This type is always 970 * `PhabricatorTransactions::TYPE_CUSTOMFIELD`, it is provided for 971 * convenience when constructing exceptions. 972 * @param list<PhabricatorApplicationTransaction> Transactions being applied, 973 * which may be empty if this field is not being edited. 974 * @return list<PhabricatorApplicationTransactionValidationError> Validation 975 * errors. 976 * 977 * @task appxaction 978 */ 979 public function validateApplicationTransactions( 980 PhabricatorApplicationTransactionEditor $editor, 981 $type, 982 array $xactions) { 983 if ($this->proxy) { 984 return $this->proxy->validateApplicationTransactions( 985 $editor, 986 $type, 987 $xactions); 988 } 989 return array(); 990 } 991 992 public function getApplicationTransactionTitle( 993 PhabricatorApplicationTransaction $xaction) { 994 if ($this->proxy) { 995 return $this->proxy->getApplicationTransactionTitle( 996 $xaction); 997 } 998 999 $author_phid = $xaction->getAuthorPHID(); 1000 return pht( 1001 '%s updated this object.', 1002 $xaction->renderHandleLink($author_phid)); 1003 } 1004 1005 public function getApplicationTransactionTitleForFeed( 1006 PhabricatorApplicationTransaction $xaction, 1007 PhabricatorFeedStory $story) { 1008 if ($this->proxy) { 1009 return $this->proxy->getApplicationTransactionTitleForFeed( 1010 $xaction, 1011 $story); 1012 } 1013 1014 $author_phid = $xaction->getAuthorPHID(); 1015 $object_phid = $xaction->getObjectPHID(); 1016 return pht( 1017 '%s updated %s.', 1018 $xaction->renderHandleLink($author_phid), 1019 $xaction->renderHandleLink($object_phid)); 1020 } 1021 1022 1023 public function getApplicationTransactionHasChangeDetails( 1024 PhabricatorApplicationTransaction $xaction) { 1025 if ($this->proxy) { 1026 return $this->proxy->getApplicationTransactionHasChangeDetails( 1027 $xaction); 1028 } 1029 return false; 1030 } 1031 1032 public function getApplicationTransactionChangeDetails( 1033 PhabricatorApplicationTransaction $xaction, 1034 PhabricatorUser $viewer) { 1035 if ($this->proxy) { 1036 return $this->proxy->getApplicationTransactionChangeDetails( 1037 $xaction, 1038 $viewer); 1039 } 1040 return null; 1041 } 1042 1043 public function getApplicationTransactionRequiredHandlePHIDs( 1044 PhabricatorApplicationTransaction $xaction) { 1045 if ($this->proxy) { 1046 return $this->proxy->getApplicationTransactionRequiredHandlePHIDs( 1047 $xaction); 1048 } 1049 return array(); 1050 } 1051 1052 public function shouldHideInApplicationTransactions( 1053 PhabricatorApplicationTransaction $xaction) { 1054 if ($this->proxy) { 1055 return $this->proxy->shouldHideInApplicationTransactions($xaction); 1056 } 1057 return false; 1058 } 1059 1060 /** 1061 * TODO: this is only used by Diffusion right now and everything is completely 1062 * faked since Diffusion doesn't use ApplicationTransactions yet. This should 1063 * get fleshed out as we have more use cases. 1064 * 1065 * @task appxaction 1066 */ 1067 public function buildApplicationTransactionMailBody( 1068 PhabricatorApplicationTransaction $xaction, 1069 PhabricatorMetaMTAMailBody $body) { 1070 if ($this->proxy) { 1071 return $this->proxy->buildApplicationTransactionMailBody($xaction, $body); 1072 } 1073 return; 1074 } 1075 1076 1077 /* -( Transaction Mail )--------------------------------------------------- */ 1078 1079 1080 /** 1081 * @task xactionmail 1082 */ 1083 public function shouldAppearInTransactionMail() { 1084 if ($this->proxy) { 1085 return $this->proxy->shouldAppearInTransactionMail(); 1086 } 1087 return false; 1088 } 1089 1090 1091 /** 1092 * @task xactionmail 1093 */ 1094 public function updateTransactionMailBody( 1095 PhabricatorMetaMTAMailBody $body, 1096 PhabricatorApplicationTransactionEditor $editor, 1097 array $xactions) { 1098 if ($this->proxy) { 1099 return $this->proxy->updateTransactionMailBody($body, $editor, $xactions); 1100 } 1101 return; 1102 } 1103 1104 1105 /* -( Edit View )---------------------------------------------------------- */ 1106 1107 1108 /** 1109 * @task edit 1110 */ 1111 public function shouldAppearInEditView() { 1112 if ($this->proxy) { 1113 return $this->proxy->shouldAppearInEditView(); 1114 } 1115 return false; 1116 } 1117 1118 1119 /** 1120 * @task edit 1121 */ 1122 public function readValueFromRequest(AphrontRequest $request) { 1123 if ($this->proxy) { 1124 return $this->proxy->readValueFromRequest($request); 1125 } 1126 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 1127 } 1128 1129 1130 /** 1131 * @task edit 1132 */ 1133 public function getRequiredHandlePHIDsForEdit() { 1134 if ($this->proxy) { 1135 return $this->proxy->getRequiredHandlePHIDsForEdit(); 1136 } 1137 return array(); 1138 } 1139 1140 1141 /** 1142 * @task edit 1143 */ 1144 public function getInstructionsForEdit() { 1145 if ($this->proxy) { 1146 return $this->proxy->getInstructionsForEdit(); 1147 } 1148 return null; 1149 } 1150 1151 1152 /** 1153 * @task edit 1154 */ 1155 public function renderEditControl(array $handles) { 1156 if ($this->proxy) { 1157 return $this->proxy->renderEditControl($handles); 1158 } 1159 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 1160 } 1161 1162 1163 /* -( Property View )------------------------------------------------------ */ 1164 1165 1166 /** 1167 * @task view 1168 */ 1169 public function shouldAppearInPropertyView() { 1170 if ($this->proxy) { 1171 return $this->proxy->shouldAppearInPropertyView(); 1172 } 1173 return false; 1174 } 1175 1176 1177 /** 1178 * @task view 1179 */ 1180 public function renderPropertyViewLabel() { 1181 if ($this->proxy) { 1182 return $this->proxy->renderPropertyViewLabel(); 1183 } 1184 return $this->getFieldName(); 1185 } 1186 1187 1188 /** 1189 * @task view 1190 */ 1191 public function renderPropertyViewValue(array $handles) { 1192 if ($this->proxy) { 1193 return $this->proxy->renderPropertyViewValue($handles); 1194 } 1195 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 1196 } 1197 1198 1199 /** 1200 * @task view 1201 */ 1202 public function getStyleForPropertyView() { 1203 if ($this->proxy) { 1204 return $this->proxy->getStyleForPropertyView(); 1205 } 1206 return 'property'; 1207 } 1208 1209 1210 /** 1211 * @task view 1212 */ 1213 public function getIconForPropertyView() { 1214 if ($this->proxy) { 1215 return $this->proxy->getIconForPropertyView(); 1216 } 1217 return null; 1218 } 1219 1220 1221 /** 1222 * @task view 1223 */ 1224 public function getRequiredHandlePHIDsForPropertyView() { 1225 if ($this->proxy) { 1226 return $this->proxy->getRequiredHandlePHIDsForPropertyView(); 1227 } 1228 return array(); 1229 } 1230 1231 1232 /* -( List View )---------------------------------------------------------- */ 1233 1234 1235 /** 1236 * @task list 1237 */ 1238 public function shouldAppearInListView() { 1239 if ($this->proxy) { 1240 return $this->proxy->shouldAppearInListView(); 1241 } 1242 return false; 1243 } 1244 1245 1246 /** 1247 * @task list 1248 */ 1249 public function renderOnListItem(PHUIObjectItemView $view) { 1250 if ($this->proxy) { 1251 return $this->proxy->renderOnListItem($view); 1252 } 1253 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 1254 } 1255 1256 1257 /* -( Global Search )------------------------------------------------------ */ 1258 1259 1260 /** 1261 * @task globalsearch 1262 */ 1263 public function shouldAppearInGlobalSearch() { 1264 if ($this->proxy) { 1265 return $this->proxy->shouldAppearInGlobalSearch(); 1266 } 1267 return false; 1268 } 1269 1270 1271 /** 1272 * @task globalsearch 1273 */ 1274 public function updateAbstractDocument( 1275 PhabricatorSearchAbstractDocument $document) { 1276 if ($this->proxy) { 1277 return $this->proxy->updateAbstractDocument($document); 1278 } 1279 return $document; 1280 } 1281 1282 1283 /* -( Conduit )------------------------------------------------------------ */ 1284 1285 1286 /** 1287 * @task conduit 1288 */ 1289 public function shouldAppearInConduitDictionary() { 1290 if ($this->proxy) { 1291 return $this->proxy->shouldAppearInConduitDictionary(); 1292 } 1293 return false; 1294 } 1295 1296 1297 /** 1298 * @task conduit 1299 */ 1300 public function getConduitDictionaryValue() { 1301 if ($this->proxy) { 1302 return $this->proxy->getConduitDictionaryValue(); 1303 } 1304 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 1305 } 1306 1307 1308 /* -( Herald )------------------------------------------------------------- */ 1309 1310 1311 /** 1312 * Return `true` to make this field available in Herald. 1313 * 1314 * @return bool True to expose the field in Herald. 1315 * @task herald 1316 */ 1317 public function shouldAppearInHerald() { 1318 if ($this->proxy) { 1319 return $this->proxy->shouldAppearInHerald(); 1320 } 1321 return false; 1322 } 1323 1324 1325 /** 1326 * Get the name of the field in Herald. By default, this uses the 1327 * normal field name. 1328 * 1329 * @return string Herald field name. 1330 * @task herald 1331 */ 1332 public function getHeraldFieldName() { 1333 if ($this->proxy) { 1334 return $this->proxy->getHeraldFieldName(); 1335 } 1336 return $this->getFieldName(); 1337 } 1338 1339 1340 /** 1341 * Get the field value for evaluation by Herald. 1342 * 1343 * @return wild Field value. 1344 * @task herald 1345 */ 1346 public function getHeraldFieldValue() { 1347 if ($this->proxy) { 1348 return $this->proxy->getHeraldFieldValue(); 1349 } 1350 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 1351 } 1352 1353 1354 /** 1355 * Get the available conditions for this field in Herald. 1356 * 1357 * @return list<const> List of Herald condition constants. 1358 * @task herald 1359 */ 1360 public function getHeraldFieldConditions() { 1361 if ($this->proxy) { 1362 return $this->proxy->getHeraldFieldConditions(); 1363 } 1364 throw new PhabricatorCustomFieldImplementationIncompleteException($this); 1365 } 1366 1367 1368 /** 1369 * Get the Herald value type for the given condition. 1370 * 1371 * @param const Herald condition constant. 1372 * @return const|null Herald value type, or null to use the default. 1373 * @task herald 1374 */ 1375 public function getHeraldFieldValueType($condition) { 1376 if ($this->proxy) { 1377 return $this->proxy->getHeraldFieldValueType($condition); 1378 } 1379 return null; 1380 } 1381 1382 1383 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |