[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/infrastructure/customfield/field/ -> PhabricatorCustomField.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1