2011-08-10 20:29:08 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright 2011 Facebook, Inc.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2011-08-10 22:46:01 +02:00
|
|
|
/**
|
|
|
|
* Describes and implements the behavior for a custom field on Differential
|
|
|
|
* revisions. Along with other configuration, you can extend this class to add
|
|
|
|
* custom fields to Differential revisions and commit messages.
|
|
|
|
*
|
|
|
|
* Generally, you should implement all methods from the storage task and then
|
|
|
|
* the methods from one or more interface tasks.
|
|
|
|
*
|
|
|
|
* @task storage Field Storage
|
|
|
|
* @task edit Extending the Revision Edit Interface
|
|
|
|
* @task view Extending the Revision View Interface
|
2011-08-11 00:48:44 +02:00
|
|
|
* @task conduit Extending the Conduit View Interface
|
2011-08-14 21:33:54 +02:00
|
|
|
* @task handles Loading Handles
|
2011-08-14 20:29:56 +02:00
|
|
|
* @task context Contextual Data
|
2011-08-10 22:46:01 +02:00
|
|
|
*/
|
2011-08-10 20:29:08 +02:00
|
|
|
abstract class DifferentialFieldSpecification {
|
|
|
|
|
2011-08-14 20:29:56 +02:00
|
|
|
private $revision;
|
|
|
|
private $diff;
|
2011-08-14 21:33:54 +02:00
|
|
|
private $handles;
|
2011-08-14 20:29:56 +02:00
|
|
|
|
2011-08-10 22:46:01 +02:00
|
|
|
|
|
|
|
/* -( Storage )------------------------------------------------------------ */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a unique string used to key storage of this field's value, like
|
2011-08-14 20:29:56 +02:00
|
|
|
* "mycompany.fieldname" or similar. You can return null (the default) to
|
|
|
|
* indicate that this field does not use any storage. This is appropriate for
|
|
|
|
* display fields, like @{class:DifferentialLinesFieldSpecification}. If you
|
|
|
|
* implement this, you must also implement @{method:getValueForStorage} and
|
|
|
|
* @{method:setValueFromStorage}.
|
2011-08-10 22:46:01 +02:00
|
|
|
*
|
2011-08-14 20:29:56 +02:00
|
|
|
* @return string|null Unique key which identifies this field in auxiliary
|
|
|
|
* field storage. Maximum length is 32. Alternatively,
|
|
|
|
* null (default) to indicate that this field does not
|
|
|
|
* use auxiliary field storage.
|
|
|
|
* @task storage
|
2011-08-10 22:46:01 +02:00
|
|
|
*/
|
2011-08-14 20:29:56 +02:00
|
|
|
public function getStorageKey() {
|
|
|
|
return null;
|
|
|
|
}
|
2011-08-10 22:46:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a serialized representation of the field value, appropriate for
|
2011-08-14 20:29:56 +02:00
|
|
|
* storing in auxiliary field storage. You must implement this method if
|
|
|
|
* you implement @{method:getStorageKey}.
|
2011-08-10 22:46:01 +02:00
|
|
|
*
|
|
|
|
* @return string Serialized field value.
|
2011-08-14 20:29:56 +02:00
|
|
|
* @task storage
|
2011-08-10 22:46:01 +02:00
|
|
|
*/
|
2011-08-14 20:29:56 +02:00
|
|
|
public function getValueForStorage() {
|
|
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
|
|
}
|
2011-08-10 22:46:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the field's value given a serialized storage value. This is called
|
|
|
|
* when the field is loaded; if no data is available, the value will be
|
2011-08-14 20:29:56 +02:00
|
|
|
* null. You must implement this method if you implement
|
|
|
|
* @{method:getStorageKey}.
|
2011-08-10 22:46:01 +02:00
|
|
|
*
|
|
|
|
* @param string|null Serialized field representation (from
|
2011-08-14 20:29:56 +02:00
|
|
|
* @{method:getValueForStorage}) or null if no value has
|
|
|
|
* ever been stored.
|
2011-08-10 22:46:01 +02:00
|
|
|
* @return this
|
2011-08-14 20:29:56 +02:00
|
|
|
* @task storage
|
2011-08-10 22:46:01 +02:00
|
|
|
*/
|
2011-08-14 20:29:56 +02:00
|
|
|
public function setValueFromStorage($value) {
|
|
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
|
|
}
|
2011-08-10 22:46:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* -( Extending the Revision Edit Interface )------------------------------ */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-08-14 20:29:56 +02:00
|
|
|
* Determine if this field should appear on the "Edit Revision" interface. If
|
|
|
|
* you return true from this method, you must implement
|
|
|
|
* @{method:setValueFromRequest}, @{method:renderEditControl} and
|
|
|
|
* @{method:validateField}.
|
|
|
|
*
|
|
|
|
* For a concrete example of a field which implements an edit interface, see
|
|
|
|
* @{class:DifferentialRevertPlanFieldSpecification}.
|
|
|
|
*
|
|
|
|
* @return bool True to indicate that this field implements an edit interface.
|
2011-08-10 22:46:01 +02:00
|
|
|
* @task edit
|
|
|
|
*/
|
|
|
|
public function shouldAppearOnEdit() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-08-14 20:29:56 +02:00
|
|
|
* Set the field's value from an HTTP request. Generally, you should read
|
|
|
|
* the value of some field name you emitted in @{method:renderEditControl}
|
|
|
|
* and save it into the object, e.g.:
|
|
|
|
*
|
|
|
|
* $this->value = $request->getStr('my-custom-field');
|
|
|
|
*
|
|
|
|
* If you have some particularly complicated field, you may need to read
|
|
|
|
* more data; this is why you have access to the entire request.
|
|
|
|
*
|
|
|
|
* You must implement this if you implement @{method:shouldAppearOnEdit}.
|
|
|
|
*
|
|
|
|
* You should not perform field validation here; instead, you should implement
|
|
|
|
* @{method:validateField}.
|
|
|
|
*
|
|
|
|
* @param AphrontRequest HTTP request representing a user submitting a form
|
|
|
|
* with this field in it.
|
|
|
|
* @return this
|
2011-08-10 22:46:01 +02:00
|
|
|
* @task edit
|
|
|
|
*/
|
|
|
|
public function setValueFromRequest(AphrontRequest $request) {
|
|
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-08-14 20:29:56 +02:00
|
|
|
* Build a renderable object (generally, some @{class:AphrontFormControl})
|
|
|
|
* which can be appended to a @{class:AphrontFormView} and represents the
|
|
|
|
* interface the user sees on the "Edit Revision" screen when interacting
|
|
|
|
* with this field.
|
|
|
|
*
|
|
|
|
* For example:
|
|
|
|
*
|
|
|
|
* return id(new AphrontFormTextControl())
|
|
|
|
* ->setLabel('Custom Field')
|
|
|
|
* ->setName('my-custom-key')
|
|
|
|
* ->setValue($this->value);
|
|
|
|
*
|
|
|
|
* You must implement this if you implement @{method:shouldAppearOnEdit}.
|
|
|
|
*
|
|
|
|
* @return AphrontView|string Something renderable.
|
2011-08-10 22:46:01 +02:00
|
|
|
* @task edit
|
|
|
|
*/
|
|
|
|
public function renderEditControl() {
|
|
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2011-08-14 20:29:56 +02:00
|
|
|
* This method will be called after @{method:setValueFromRequest} but before
|
|
|
|
* the field is saved. It gives you an opportunity to inspect the field value
|
|
|
|
* and throw a @{class:DifferentialFieldValidationException} if there is a
|
|
|
|
* problem with the value the user has provided (for example, the value the
|
|
|
|
* user entered is not correctly formatted).
|
|
|
|
*
|
|
|
|
* By default, fields are not validated.
|
|
|
|
*
|
|
|
|
* @return void
|
2011-08-10 22:46:01 +02:00
|
|
|
* @task edit
|
|
|
|
*/
|
|
|
|
public function validateField() {
|
2011-08-14 20:29:56 +02:00
|
|
|
return;
|
2011-08-10 22:46:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* -( Extending the Revision View Interface )------------------------------ */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task view
|
|
|
|
*/
|
|
|
|
public function shouldAppearOnRevisionView() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task view
|
|
|
|
*/
|
|
|
|
public function renderLabelForRevisionView() {
|
|
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task view
|
|
|
|
*/
|
|
|
|
public function renderValueForRevisionView() {
|
|
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-08-11 00:48:44 +02:00
|
|
|
/* -( Extending the Conduit Interface )------------------------------------ */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task conduit
|
|
|
|
*/
|
|
|
|
public function shouldAppearOnConduitView() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task conduit
|
|
|
|
*/
|
|
|
|
public function getValueForConduit() {
|
|
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
|
|
}
|
|
|
|
|
2011-08-14 20:29:56 +02:00
|
|
|
/**
|
|
|
|
* @task conduit
|
|
|
|
*/
|
|
|
|
public function getKeyForConduit() {
|
|
|
|
$key = $this->getStorageKey();
|
|
|
|
if ($key === null) {
|
|
|
|
throw new DifferentialFieldSpecificationIncompleteException($this);
|
|
|
|
}
|
|
|
|
return $key;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-08-14 21:33:54 +02:00
|
|
|
/* -( Loading Handles )---------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specify which @{class:PhabricatorObjectHandles} need to be loaded for your
|
|
|
|
* field to render correctly.
|
|
|
|
*
|
|
|
|
* This is a convenience method which makes the handles available on all
|
|
|
|
* interfaces where the field appears. If your field needs handles on only
|
|
|
|
* some interfaces (or needs different handles on different interfaces) you
|
|
|
|
* can overload the more specific methods to customize which interfaces you
|
|
|
|
* retrieve handles for. Requesting only the handles you need will improve
|
|
|
|
* the performance of your field.
|
|
|
|
*
|
|
|
|
* You can later retrieve these handles by calling @{method:getHandle}.
|
|
|
|
*
|
|
|
|
* @return list List of PHIDs to load handles for.
|
|
|
|
* @task handles
|
|
|
|
*/
|
|
|
|
protected function getRequiredHandlePHIDs() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specify which @{class:PhabricatorObjectHandles} need to be loaded for your
|
|
|
|
* field to render correctly on the view interface.
|
|
|
|
*
|
|
|
|
* This is a more specific version of @{method:getRequiredHandlePHIDs} which
|
|
|
|
* can be overridden to improve field performance by loading only data you
|
|
|
|
* need.
|
|
|
|
*
|
|
|
|
* @return list List of PHIDs to load handles for.
|
|
|
|
* @task handles
|
|
|
|
*/
|
|
|
|
public function getRequiredHandlePHIDsForRevisionView() {
|
|
|
|
return $this->getRequiredHandlePHIDs();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specify which @{class:PhabricatorObjectHandles} need to be loaded for your
|
|
|
|
* field to render correctly on the edit interface.
|
|
|
|
*
|
|
|
|
* This is a more specific version of @{method:getRequiredHandlePHIDs} which
|
|
|
|
* can be overridden to improve field performance by loading only data you
|
|
|
|
* need.
|
|
|
|
*
|
|
|
|
* @return list List of PHIDs to load handles for.
|
|
|
|
* @task handles
|
|
|
|
*/
|
|
|
|
public function getRequiredHandlePHIDsForEdit() {
|
|
|
|
return $this->getRequiredHandlePHIDs();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-08-14 20:29:56 +02:00
|
|
|
/* -( Contextual Data )---------------------------------------------------- */
|
|
|
|
|
2011-08-14 21:33:54 +02:00
|
|
|
|
2011-08-14 20:29:56 +02:00
|
|
|
/**
|
|
|
|
* @task context
|
|
|
|
*/
|
|
|
|
final public function setRevision(DifferentialRevision $revision) {
|
|
|
|
$this->revision = $revision;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task context
|
|
|
|
*/
|
|
|
|
final public function setDiff(DifferentialDiff $diff) {
|
|
|
|
$this->diff = $diff;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-08-14 21:33:54 +02:00
|
|
|
/**
|
|
|
|
* @task context
|
|
|
|
*/
|
|
|
|
final public function setHandles(array $handles) {
|
|
|
|
$this->handles = $handles;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-08-14 20:29:56 +02:00
|
|
|
/**
|
|
|
|
* @task context
|
|
|
|
*/
|
|
|
|
final protected function getRevision() {
|
|
|
|
if (empty($this->revision)) {
|
|
|
|
throw new DifferentialFieldDataNotAvailableException($this);
|
|
|
|
}
|
|
|
|
return $this->revision;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task context
|
|
|
|
*/
|
|
|
|
final protected function getDiff() {
|
|
|
|
if (empty($this->diff)) {
|
|
|
|
throw new DifferentialFieldDataNotAvailableException($this);
|
|
|
|
}
|
|
|
|
return $this->diff;
|
|
|
|
}
|
|
|
|
|
2011-08-14 21:33:54 +02:00
|
|
|
/**
|
|
|
|
* Get the handle for an object PHID. You must overload
|
|
|
|
* @{method:getRequiredHandlePHIDs} (or a more specific version thereof)
|
|
|
|
* and include the PHID you want in the list for it to be available here.
|
|
|
|
*
|
|
|
|
* @return PhabricatorObjectHandle Handle to the object.
|
|
|
|
* @task context
|
|
|
|
*/
|
|
|
|
final protected function getHandle($phid) {
|
|
|
|
if (empty($this->handles[$phid])) {
|
|
|
|
$class = get_class($this);
|
|
|
|
throw new Exception(
|
|
|
|
"A differential field (of class '{$class}') is attempting to retrieve ".
|
|
|
|
"a handle ('{$phid}') which it did not request. Return all handle ".
|
|
|
|
"PHIDs you need from getRequiredHandlePHIDs().");
|
|
|
|
}
|
|
|
|
return $this->handles[$phid];
|
|
|
|
}
|
|
|
|
|
2011-08-10 20:29:08 +02:00
|
|
|
}
|