Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/conduit/ConduitAPI_differential_Method.php b/src/applications/differential/conduit/ConduitAPI_differential_Method.php
index ee264b8f64..0cd61548ba 100644
--- a/src/applications/differential/conduit/ConduitAPI_differential_Method.php
+++ b/src/applications/differential/conduit/ConduitAPI_differential_Method.php
@@ -1,47 +1,151 @@
<?php
abstract class ConduitAPI_differential_Method extends ConduitAPIMethod {
public function getApplication() {
return PhabricatorApplication::getByClass(
'PhabricatorApplicationDifferential');
}
protected function buildDiffInfoDictionary(DifferentialDiff $diff) {
$uri = '/differential/diff/'.$diff->getID().'/';
$uri = PhabricatorEnv::getProductionURI($uri);
return array(
'id' => $diff->getID(),
'uri' => $uri,
);
}
protected function buildInlineInfoDictionary(
DifferentialInlineComment $inline,
DifferentialChangeset $changeset = null) {
$file_path = null;
$diff_id = null;
if ($changeset) {
$file_path = $inline->getIsNewFile()
? $changeset->getFilename()
: $changeset->getOldFile();
$diff_id = $changeset->getDiffID();
}
return array(
'id' => $inline->getID(),
'authorPHID' => $inline->getAuthorPHID(),
'filePath' => $file_path,
'isNewFile' => $inline->getIsNewFile(),
'lineNumber' => $inline->getLineNumber(),
'lineLength' => $inline->getLineLength(),
'diffID' => $diff_id,
'content' => $inline->getContent(),
);
}
+ protected function applyFieldEdit(
+ ConduitAPIRequest $request,
+ DifferentialRevision $revision,
+ DifferentialDiff $diff,
+ array $fields,
+ $message) {
+
+ $viewer = $request->getUser();
+
+ $field_list = PhabricatorCustomField::getObjectFields(
+ $revision,
+ DifferentialCustomField::ROLE_COMMITMESSAGEEDIT);
+
+ $field_list
+ ->setViewer($viewer)
+ ->readFieldsFromStorage($revision);
+ $field_map = mpull($field_list->getFields(), null, 'getFieldKeyForConduit');
+
+ $xactions = array();
+
+ $xactions[] = id(new DifferentialTransaction())
+ ->setTransactionType(DifferentialTransaction::TYPE_UPDATE)
+ ->setNewValue($diff->getPHID());
+
+ $values = $request->getValue('fields', array());
+ foreach ($values as $key => $value) {
+ $field = idx($field_map, $key);
+ if (!$field) {
+ // NOTE: We're just ignoring fields we don't know about. This isn't
+ // ideal, but the way the workflow currently works involves us getting
+ // several read-only fields, like the revision ID field, which we should
+ // just skip.
+ continue;
+ }
+
+ $role = PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS;
+ if (!$field->shouldEnableForRole($role)) {
+ throw new Exception(
+ pht(
+ 'Request attempts to update field "%s", but that field can not '.
+ 'perform transactional updates.',
+ $key));
+ }
+
+ // TODO: This is fairly similar to PhabricatorCustomField's
+ // buildFieldTransactionsFromRequest() method, but that's currently not
+ // easy to reuse.
+
+ $transaction_type = $field->getApplicationTransactionType();
+ $xaction = id(new DifferentialTransaction())
+ ->setTransactionType($transaction_type);
+
+ if ($transaction_type == PhabricatorTransactions::TYPE_CUSTOMFIELD) {
+ // For TYPE_CUSTOMFIELD transactions only, we provide the old value
+ // as an input.
+ $old_value = $field->getOldValueForApplicationTransactions();
+ $xaction->setOldValue($old_value);
+ }
+
+ // The transaction itself will be validated so this is somewhat
+ // redundant, but this validator will sometimes give us a better error
+ // message or a better reaction to a bad value type.
+ $field->validateCommitMessageValue($value);
+ $field->readValueFromCommitMessage($value);
+
+ $xaction
+ ->setNewValue($field->getNewValueForApplicationTransactions());
+
+ if ($transaction_type == PhabricatorTransactions::TYPE_CUSTOMFIELD) {
+ // For TYPE_CUSTOMFIELD transactions, add the field key in metadata.
+ $xaction->setMetadataValue('customfield:key', $field->getFieldKey());
+ }
+
+ $metadata = $field->getApplicationTransactionMetadata();
+ foreach ($metadata as $meta_key => $meta_value) {
+ $xaction->setMetadataValue($meta_key, $meta_value);
+ }
+
+ $xactions[] = $xaction;
+ }
+
+ $message = $request->getValue('message');
+ if (strlen($message)) {
+ // This is a little awkward, and should maybe move inside the transaction
+ // editor. It largely exists for legacy reasons.
+ $first_line = head(phutil_split_lines($message, false));
+ $diff->setDescription($first_line);
+ $diff->save();
+
+ $xactions[] = id(new DifferentialTransaction())
+ ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
+ ->attachComment(
+ id(new DifferentialTransactionComment())
+ ->setContent($message));
+ }
+
+ $editor = id(new DifferentialTransactionEditor())
+ ->setActor($viewer)
+ ->setContentSourceFromConduitRequest($request)
+ ->setContinueOnNoEffect(true)
+ ->setContinueOnMissingFields(true);
+
+ $editor->applyTransactions($revision, $xactions);
+ }
}
diff --git a/src/applications/differential/conduit/ConduitAPI_differential_createrevision_Method.php b/src/applications/differential/conduit/ConduitAPI_differential_createrevision_Method.php
index d6b47b58bf..deb4999b0b 100644
--- a/src/applications/differential/conduit/ConduitAPI_differential_createrevision_Method.php
+++ b/src/applications/differential/conduit/ConduitAPI_differential_createrevision_Method.php
@@ -1,55 +1,57 @@
<?php
-/**
- * @group conduit
- */
final class ConduitAPI_differential_createrevision_Method
- extends ConduitAPIMethod {
+ extends ConduitAPI_differential_Method {
public function getMethodDescription() {
- return "Create a new Differential revision.";
+ return pht("Create a new Differential revision.");
}
public function defineParamTypes() {
return array(
// TODO: Arcanist passes this; prevent fatals after D4191 until Conduit
// version 7 or newer.
'user' => 'ignored',
'diffid' => 'required diffid',
'fields' => 'required dict',
);
}
public function defineReturnType() {
return 'nonempty dict';
}
public function defineErrorTypes() {
return array(
'ERR_BAD_DIFF' => 'Bad diff ID.',
);
}
protected function execute(ConduitAPIRequest $request) {
- $fields = $request->getValue('fields');
+ $viewer = $request->getUser();
$diff = id(new DifferentialDiffQuery())
- ->setViewer($request->getUser())
+ ->setViewer($viewer)
->withIDs(array($request->getValue('diffid')))
->executeOne();
if (!$diff) {
throw new ConduitException('ERR_BAD_DIFF');
}
- $revision = DifferentialRevisionEditor::newRevisionFromConduitWithDiff(
- $fields,
+ $revision = DifferentialRevision::initializeNewRevision($viewer);
+ $revision->attachReviewerStatus(array());
+
+ $this->applyFieldEdit(
+ $request,
+ $revision,
$diff,
- $request->getUser());
+ $request->getValue('fields', array()),
+ $message = null);
return array(
'revisionid' => $revision->getID(),
'uri' => PhabricatorEnv::getURI('/D'.$revision->getID()),
);
}
}
diff --git a/src/applications/differential/conduit/ConduitAPI_differential_updaterevision_Method.php b/src/applications/differential/conduit/ConduitAPI_differential_updaterevision_Method.php
index b29c3c8392..badd7cbb49 100644
--- a/src/applications/differential/conduit/ConduitAPI_differential_updaterevision_Method.php
+++ b/src/applications/differential/conduit/ConduitAPI_differential_updaterevision_Method.php
@@ -1,163 +1,75 @@
<?php
final class ConduitAPI_differential_updaterevision_Method
- extends ConduitAPIMethod {
+ extends ConduitAPI_differential_Method {
public function getMethodDescription() {
return pht("Update a Differential revision.");
}
public function defineParamTypes() {
return array(
'id' => 'required revisionid',
'diffid' => 'required diffid',
'fields' => 'required dict',
'message' => 'required string',
);
}
public function defineReturnType() {
return 'nonempty dict';
}
public function defineErrorTypes() {
return array(
'ERR_BAD_DIFF' => 'Bad diff ID.',
'ERR_BAD_REVISION' => 'Bad revision ID.',
'ERR_WRONG_USER' => 'You are not the author of this revision.',
'ERR_CLOSED' => 'This revision has already been closed.',
);
}
protected function execute(ConduitAPIRequest $request) {
$viewer = $request->getUser();
$diff = id(new DifferentialDiffQuery())
->setViewer($viewer)
->withIDs(array($request->getValue('diffid')))
->executeOne();
if (!$diff) {
throw new ConduitException('ERR_BAD_DIFF');
}
$revision = id(new DifferentialRevisionQuery())
->setViewer($request->getUser())
->withIDs(array($request->getValue('id')))
->needReviewerStatus(true)
->needActiveDiffs(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$revision) {
throw new ConduitException('ERR_BAD_REVISION');
}
if ($revision->getStatus() == ArcanistDifferentialRevisionStatus::CLOSED) {
throw new ConduitException('ERR_CLOSED');
}
- $field_list = PhabricatorCustomField::getObjectFields(
+ $this->applyFieldEdit(
+ $request,
$revision,
- DifferentialCustomField::ROLE_COMMITMESSAGEEDIT);
-
- $field_list
- ->setViewer($viewer)
- ->readFieldsFromStorage($revision);
- $field_map = mpull($field_list->getFields(), null, 'getFieldKeyForConduit');
-
- $xactions = array();
-
- $xactions[] = id(new DifferentialTransaction())
- ->setTransactionType(DifferentialTransaction::TYPE_UPDATE)
- ->setNewValue($diff->getPHID());
-
- $values = $request->getValue('fields', array());
- foreach ($values as $key => $value) {
- $field = idx($field_map, $key);
- if (!$field) {
- // NOTE: We're just ignoring fields we don't know about. This isn't
- // ideal, but the way the workflow currently works involves us getting
- // several read-only fields, like the revision ID field, which we should
- // just skip.
- continue;
- }
-
- $role = PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS;
- if (!$field->shouldEnableForRole($role)) {
- throw new Exception(
- pht(
- 'Request attempts to update field "%s", but that field can not '.
- 'perform transactional updates.',
- $key));
- }
-
- // TODO: This is fairly similar to PhabricatorCustomField's
- // buildFieldTransactionsFromRequest() method, but that's currently not
- // easy to reuse.
-
- $transaction_type = $field->getApplicationTransactionType();
- $xaction = id(new DifferentialTransaction())
- ->setTransactionType($transaction_type);
-
- if ($transaction_type == PhabricatorTransactions::TYPE_CUSTOMFIELD) {
- // For TYPE_CUSTOMFIELD transactions only, we provide the old value
- // as an input.
- $old_value = $field->getOldValueForApplicationTransactions();
- $xaction->setOldValue($old_value);
- }
-
- // The transaction itself will be validated so this is somewhat
- // redundant, but this validator will sometimes give us a better error
- // message or a better reaction to a bad value type.
- $field->validateCommitMessageValue($value);
- $field->readValueFromCommitMessage($value);
-
- $xaction
- ->setNewValue($field->getNewValueForApplicationTransactions());
-
- if ($transaction_type == PhabricatorTransactions::TYPE_CUSTOMFIELD) {
- // For TYPE_CUSTOMFIELD transactions, add the field key in metadata.
- $xaction->setMetadataValue('customfield:key', $field->getFieldKey());
- }
-
- $metadata = $field->getApplicationTransactionMetadata();
- foreach ($metadata as $meta_key => $meta_value) {
- $xaction->setMetadataValue($meta_key, $meta_value);
- }
-
- $xactions[] = $xaction;
- }
-
- $message = $request->getValue('message');
- if (strlen($message)) {
- // This is a little awkward, and should maybe move inside the transaction
- // editor. It largely exists for legacy reasons.
- $first_line = head(phutil_split_lines($message, false));
- $diff->setDescription($first_line);
- $diff->save();
-
- $xactions[] = id(new DifferentialTransaction())
- ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
- ->attachComment(
- id(new DifferentialTransactionComment())
- ->setContent($message));
- }
-
- $editor = id(new DifferentialTransactionEditor())
- ->setActor($viewer)
- ->setContentSourceFromConduitRequest($request)
- ->setContinueOnNoEffect(true)
- ->setContinueOnMissingFields(true);
-
- $editor->applyTransactions($revision, $xactions);
+ $diff,
+ $request->getValue('fields', array()),
+ $request->getValue('message'));
return array(
'revisionid' => $revision->getID(),
'uri' => PhabricatorEnv::getURI('/D'.$revision->getID()),
);
}
}
diff --git a/src/applications/differential/customfield/DifferentialSummaryField.php b/src/applications/differential/customfield/DifferentialSummaryField.php
index 01d43cb5a3..e47be71191 100644
--- a/src/applications/differential/customfield/DifferentialSummaryField.php
+++ b/src/applications/differential/customfield/DifferentialSummaryField.php
@@ -1,142 +1,150 @@
<?php
final class DifferentialSummaryField
extends DifferentialCoreCustomField {
public function getFieldKey() {
return 'differential:summary';
}
public function getFieldKeyForConduit() {
return 'summary';
}
public function getFieldName() {
return pht('Summary');
}
public function getFieldDescription() {
return pht('Stores a summary of the revision.');
}
protected function readValueFromRevision(
DifferentialRevision $revision) {
+ if (!$revision->getID()) {
+ return null;
+ }
return $revision->getSummary();
}
protected function writeValueToRevision(
DifferentialRevision $revision,
$value) {
$revision->setSummary($value);
}
public function readValueFromRequest(AphrontRequest $request) {
$this->setValue($request->getStr($this->getFieldKey()));
}
public function renderEditControl(array $handles) {
return id(new PhabricatorRemarkupControl())
->setName($this->getFieldKey())
->setValue($this->getValue())
->setError($this->getFieldError())
->setLabel($this->getFieldName());
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
return pht(
'%s updated the summary for this revision.',
$xaction->renderHandleLink($author_phid));
}
public function getApplicationTransactionTitleForFeed(
PhabricatorApplicationTransaction $xaction,
PhabricatorFeedStory $story) {
$object_phid = $xaction->getObjectPHID();
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
return pht(
'%s updated the summary for %s.',
$xaction->renderHandleLink($author_phid),
$xaction->renderHandleLink($object_phid));
}
public function getApplicationTransactionHasChangeDetails(
PhabricatorApplicationTransaction $xaction) {
return true;
}
public function getApplicationTransactionChangeDetails(
PhabricatorApplicationTransaction $xaction,
PhabricatorUser $viewer) {
return $xaction->renderTextCorpusChangeDetails(
$viewer,
$xaction->getOldValue(),
$xaction->getNewValue());
}
+ public function shouldHideInApplicationTransactions(
+ PhabricatorApplicationTransaction $xaction) {
+ return ($xaction->getOldValue() === null);
+ }
+
public function shouldAppearInGlobalSearch() {
return true;
}
public function updateAbstractDocument(
PhabricatorSearchAbstractDocument $document) {
if (strlen($this->getValue())) {
$document->addField('body', $this->getValue());
}
}
public function shouldAppearInPropertyView() {
return true;
}
public function renderPropertyViewLabel() {
return $this->getFieldName();
}
public function getStyleForPropertyView() {
return 'block';
}
public function getIconForPropertyView() {
return PHUIPropertyListView::ICON_SUMMARY;
}
public function renderPropertyViewValue(array $handles) {
if (!strlen($this->getValue())) {
return null;
}
return PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())
->setPreserveLinebreaks(true)
->setContent($this->getValue()),
'default',
$this->getViewer());
}
public function getApplicationTransactionRemarkupBlocks(
PhabricatorApplicationTransaction $xaction) {
return array($xaction->getNewValue());
}
public function shouldAppearInCommitMessage() {
return true;
}
public function shouldAppearInCommitMessageTemplate() {
return true;
}
public function shouldOverwriteWhenCommitMessageIsEdited() {
return true;
}
}
diff --git a/src/applications/differential/customfield/DifferentialTestPlanField.php b/src/applications/differential/customfield/DifferentialTestPlanField.php
index 5b6ffe7ed1..d20b354901 100644
--- a/src/applications/differential/customfield/DifferentialTestPlanField.php
+++ b/src/applications/differential/customfield/DifferentialTestPlanField.php
@@ -1,174 +1,181 @@
<?php
final class DifferentialTestPlanField
extends DifferentialCoreCustomField {
public function getFieldKey() {
return 'differential:test-plan';
}
public function getFieldKeyForConduit() {
return 'testPlan';
}
public function getFieldName() {
return pht('Test Plan');
}
public function getFieldDescription() {
return pht('Actions performed to verify the behavior of the change.');
}
protected function readValueFromRevision(
DifferentialRevision $revision) {
+ if (!$revision->getID()) {
+ return null;
+ }
return $revision->getTestPlan();
}
protected function writeValueToRevision(
DifferentialRevision $revision,
$value) {
$revision->setTestPlan($value);
}
protected function isCoreFieldRequired() {
return PhabricatorEnv::getEnvConfig('differential.require-test-plan-field');
}
public function canDisableField() {
return true;
}
protected function getCoreFieldRequiredErrorString() {
return pht(
'You must provide a test plan. Describe the actions you performed '.
'to verify the behvaior of this change.');
}
public function readValueFromRequest(AphrontRequest $request) {
$this->setValue($request->getStr($this->getFieldKey()));
}
public function renderEditControl(array $handles) {
return id(new PhabricatorRemarkupControl())
->setName($this->getFieldKey())
->setValue($this->getValue())
->setError($this->getFieldError())
->setLabel($this->getFieldName());
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
return pht(
'%s updated the test plan for this revision.',
$xaction->renderHandleLink($author_phid));
}
public function getApplicationTransactionTitleForFeed(
PhabricatorApplicationTransaction $xaction,
PhabricatorFeedStory $story) {
$object_phid = $xaction->getObjectPHID();
$author_phid = $xaction->getAuthorPHID();
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
return pht(
'%s updated the test plan for %s.',
$xaction->renderHandleLink($author_phid),
$xaction->renderHandleLink($object_phid));
}
public function getApplicationTransactionHasChangeDetails(
PhabricatorApplicationTransaction $xaction) {
return true;
}
public function getApplicationTransactionChangeDetails(
PhabricatorApplicationTransaction $xaction,
PhabricatorUser $viewer) {
return $xaction->renderTextCorpusChangeDetails(
$viewer,
$xaction->getOldValue(),
$xaction->getNewValue());
}
+ public function shouldHideInApplicationTransactions(
+ PhabricatorApplicationTransaction $xaction) {
+ return ($xaction->getOldValue() === null);
+ }
public function shouldAppearInGlobalSearch() {
return true;
}
public function updateAbstractDocument(
PhabricatorSearchAbstractDocument $document) {
if (strlen($this->getValue())) {
$document->addField('plan', $this->getValue());
}
}
public function shouldAppearInPropertyView() {
return true;
}
public function renderPropertyViewLabel() {
return $this->getFieldName();
}
public function getStyleForPropertyView() {
return 'block';
}
public function getIconForPropertyView() {
return PHUIPropertyListView::ICON_TESTPLAN;
}
public function renderPropertyViewValue(array $handles) {
if (!strlen($this->getValue())) {
return null;
}
return PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())
->setPreserveLinebreaks(true)
->setContent($this->getValue()),
'default',
$this->getViewer());
}
public function getApplicationTransactionRemarkupBlocks(
PhabricatorApplicationTransaction $xaction) {
return array($xaction->getNewValue());
}
public function shouldAppearInCommitMessage() {
return true;
}
public function shouldAppearInCommitMessageTemplate() {
return true;
}
public function shouldOverwriteWhenCommitMessageIsEdited() {
return true;
}
public function getCommitMessageLabels() {
return array(
'Test Plan',
'Testplan',
'Tested',
'Tests',
);
}
public function validateCommitMessageValue($value) {
if (!strlen($value) && $this->isCoreFieldRequired()) {
throw new DifferentialFieldValidationException(
$this->getCoreFieldRequiredErrorString());
}
}
}
diff --git a/src/applications/differential/storage/DifferentialTransaction.php b/src/applications/differential/storage/DifferentialTransaction.php
index 38f5e626ca..50560f7d28 100644
--- a/src/applications/differential/storage/DifferentialTransaction.php
+++ b/src/applications/differential/storage/DifferentialTransaction.php
@@ -1,454 +1,467 @@
<?php
final class DifferentialTransaction extends PhabricatorApplicationTransaction {
const TYPE_INLINE = 'differential:inline';
const TYPE_UPDATE = 'differential:update';
const TYPE_ACTION = 'differential:action';
const TYPE_STATUS = 'differential:status';
public function getApplicationName() {
return 'differential';
}
public function getApplicationTransactionType() {
return DifferentialPHIDTypeRevision::TYPECONST;
}
public function getApplicationTransactionCommentObject() {
return new DifferentialTransactionComment();
}
public function shouldHide() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
switch ($this->getTransactionType()) {
+ case self::TYPE_UPDATE:
+ // Older versions of this transaction have an ID for the new value,
+ // and/or do not record the old value. Only hide the transaction if
+ // the new value is a PHID, indicating that this is a newer style
+ // transaction.
+ if ($old === null) {
+ if (phid_get_type($new) == DifferentialPHIDTypeDiff::TYPECONST) {
+ return true;
+ }
+ }
+ break;
+
case PhabricatorTransactions::TYPE_EDGE:
- $old = $this->getOldValue();
- $new = $this->getNewValue();
$add = array_diff_key($new, $old);
$rem = array_diff_key($old, $new);
// Hide metadata-only edge transactions. These correspond to users
// accepting or rejecting revisions, but the change is always explicit
// because of the TYPE_ACTION transaction. Rendering these transactions
// just creates clutter.
if (!$add && !$rem) {
return true;
}
break;
}
- return false;
+ return parent::shouldHide();
}
public function shouldHideForMail(array $xactions) {
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
// Hide inlines when rendering mail transactions if any other
// transaction type exists.
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() != self::TYPE_INLINE) {
return true;
}
}
// If only inline transactions exist, we just render the first one.
return ($this !== head($xactions));
}
return $this->shouldHide();
}
public function getBodyForMail() {
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
// Don't render inlines into the mail body; they render into a special
// section immediately after the body instead.
return null;
}
return parent::getBodyForMail();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_UPDATE:
if ($new) {
$phids[] = $new;
}
break;
}
return $phids;
}
public function getActionName() {
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
return pht('Commented On');
case self::TYPE_UPDATE:
return pht('Updated');
case self::TYPE_ACTION:
$map = array(
DifferentialAction::ACTION_ACCEPT => pht('Accepted'),
DifferentialAction::ACTION_REJECT => pht('Requested Changes To'),
DifferentialAction::ACTION_RETHINK => pht('Planned Changes To'),
DifferentialAction::ACTION_ABANDON => pht('Abandoned'),
DifferentialAction::ACTION_CLOSE => pht('Closed'),
DifferentialAction::ACTION_REQUEST => pht('Requested A Review Of'),
DifferentialAction::ACTION_RESIGN => pht('Resigned From'),
DifferentialAction::ACTION_ADDREVIEWERS => pht('Added Reviewers'),
DifferentialAction::ACTION_CLAIM => pht('Commandeered'),
DifferentialAction::ACTION_REOPEN => pht('Reopened'),
);
$name = idx($map, $this->getNewValue());
if ($name !== null) {
return $name;
}
break;
}
return parent::getActionName();
}
public function getMailTags() {
$tags = array();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_SUBSCRIBERS;
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_CC;
break;
case self::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_CLOSED;
break;
}
break;
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER:
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_REVIEWERS;
break;
}
break;
case PhabricatorTransactions::TYPE_COMMENT:
case self::TYPE_INLINE:
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMENT;
break;
}
if (!$tags) {
$tags[] = MetaMTANotificationType::TYPE_DIFFERENTIAL_OTHER;
}
return $tags;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$author_handle = $this->renderHandleLink($author_phid);
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
return pht(
'%s added inline comments.',
$author_handle);
case self::TYPE_UPDATE:
if ($new) {
// TODO: Migrate to PHIDs and use handles here?
// TODO: Link this?
if (phid_get_type($new) == 'DIFF') {
return pht(
'%s updated this revision to %s.',
$author_handle,
$this->renderHandleLink($new));
} else {
return pht(
'%s updated this revision.',
$author_handle);
}
} else {
return pht(
'%s updated this revision.',
$author_handle);
}
case self::TYPE_ACTION:
return DifferentialAction::getBasicStoryText($new, $author_handle);
case self::TYPE_STATUS:
switch ($this->getNewValue()) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
return pht(
'This revision is now accepted and ready to land.');
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
return pht(
'This revision now requires changes to proceed.');
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
return pht(
'This revision now requires review to proceed.');
}
}
return parent::getTitle();
}
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$author_link = $this->renderHandleLink($author_phid);
$object_link = $this->renderHandleLink($object_phid);
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
return pht(
'%s added inline comments to %s.',
$author_link,
$object_link);
case self::TYPE_UPDATE:
return pht(
'%s updated the diff for %s.',
$author_link,
$object_link);
case self::TYPE_ACTION:
switch ($new) {
case DifferentialAction::ACTION_ACCEPT:
return pht(
'%s accepted %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_REJECT:
return pht(
'%s requested changes to %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_RETHINK:
return pht(
'%s planned changes to %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_ABANDON:
return pht(
'%s abandoned %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_CLOSE:
return pht(
'%s closed %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_REQUEST:
return pht(
'%s requested review of %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_RECLAIM:
return pht(
'%s reclaimed %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_RESIGN:
return pht(
'%s resigned from %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_CLAIM:
return pht(
'%s commandeered %s.',
$author_link,
$object_link);
case DifferentialAction::ACTION_REOPEN:
return pht(
'%s reopened %s.',
$author_link,
$object_link);
}
break;
case self::TYPE_STATUS:
switch ($this->getNewValue()) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
return pht(
'%s is now accepted and ready to land.',
$object_link);
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
return pht(
'%s now requires changes to proceed.',
$object_link);
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
return pht(
'%s now requires review to proceed.',
$object_link);
}
}
return parent::getTitleForFeed($story);
}
public function getIcon() {
switch ($this->getTransactionType()) {
case self::TYPE_INLINE:
return 'comment';
case self::TYPE_UPDATE:
return 'refresh';
case self::TYPE_STATUS:
switch ($this->getNewValue()) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
return 'enable';
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
return 'delete';
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
return 'refresh';
}
break;
case self::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
return 'ok';
case DifferentialAction::ACTION_ACCEPT:
return 'enable';
case DifferentialAction::ACTION_REJECT:
case DifferentialAction::ACTION_ABANDON:
return 'delete';
case DifferentialAction::ACTION_RETHINK:
return 'disable';
case DifferentialAction::ACTION_REQUEST:
return 'refresh';
case DifferentialAction::ACTION_RECLAIM:
case DifferentialAction::ACTION_REOPEN:
return 'new';
case DifferentialAction::ACTION_RESIGN:
return 'undo';
case DifferentialAction::ACTION_CLAIM:
return 'user';
}
}
return parent::getIcon();
}
public function shouldDisplayGroupWith(array $group) {
// Never group status changes with other types of actions, they're indirect
// and don't make sense when combined with direct actions.
$type_status = self::TYPE_STATUS;
if ($this->getTransactionType() == $type_status) {
return false;
}
foreach ($group as $xaction) {
if ($xaction->getTransactionType() == $type_status) {
return false;
}
}
return parent::shouldDisplayGroupWith($group);
}
public function getColor() {
switch ($this->getTransactionType()) {
case self::TYPE_UPDATE:
return PhabricatorTransactions::COLOR_SKY;
case self::TYPE_STATUS:
switch ($this->getNewValue()) {
case ArcanistDifferentialRevisionStatus::ACCEPTED:
return PhabricatorTransactions::COLOR_GREEN;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
return PhabricatorTransactions::COLOR_RED;
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
return PhabricatorTransactions::COLOR_ORANGE;
}
break;
case self::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
return PhabricatorTransactions::COLOR_BLUE;
case DifferentialAction::ACTION_ACCEPT:
return PhabricatorTransactions::COLOR_GREEN;
case DifferentialAction::ACTION_REJECT:
return PhabricatorTransactions::COLOR_RED;
case DifferentialAction::ACTION_ABANDON:
return PhabricatorTransactions::COLOR_BLACK;
case DifferentialAction::ACTION_RETHINK:
return PhabricatorTransactions::COLOR_RED;
case DifferentialAction::ACTION_REQUEST:
return PhabricatorTransactions::COLOR_SKY;
case DifferentialAction::ACTION_RECLAIM:
return PhabricatorTransactions::COLOR_SKY;
case DifferentialAction::ACTION_REOPEN:
return PhabricatorTransactions::COLOR_SKY;
case DifferentialAction::ACTION_RESIGN:
return PhabricatorTransactions::COLOR_ORANGE;
case DifferentialAction::ACTION_CLAIM:
return PhabricatorTransactions::COLOR_YELLOW;
}
}
return parent::getColor();
}
public function getNoEffectDescription() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER:
return pht(
'The reviewers you are trying to add are already reviewing '.
'this revision.');
}
break;
case DifferentialTransaction::TYPE_ACTION:
switch ($this->getNewValue()) {
case DifferentialAction::ACTION_CLOSE:
return pht('This revision is already closed.');
case DifferentialAction::ACTION_ABANDON:
return pht('This revision has already been abandoned.');
case DifferentialAction::ACTION_RECLAIM:
return pht(
'You can not reclaim this revision because his revision is '.
'not abandoned.');
case DifferentialAction::ACTION_REOPEN:
return pht(
'You can not reopen this revision because this revision is '.
'not closed.');
case DifferentialAction::ACTION_RETHINK:
return pht('This revision already requires changes.');
case DifferentialAction::ACTION_REQUEST:
return pht('Review is already requested for this revision.');
case DifferentialAction::ACTION_RESIGN:
return pht(
'You can not resign from this revision because you are not '.
'a reviewer.');
case DifferentialAction::ACTION_CLAIM:
return pht(
'You can not commandeer this revision because you already own '.
'it.');
case DifferentialAction::ACTION_ACCEPT:
return pht(
'You have already accepted this revision.');
case DifferentialAction::ACTION_REJECT:
return pht(
'You have already requested changes to this revision.');
}
break;
}
return parent::getNoEffectDescription();
}
}
diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
index 3da2c2ab7d..b2672e4373 100644
--- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -1,773 +1,778 @@
<?php
abstract class PhabricatorApplicationTransaction
extends PhabricatorLiskDAO
implements PhabricatorPolicyInterface {
const TARGET_TEXT = 'text';
const TARGET_HTML = 'html';
protected $phid;
protected $objectPHID;
protected $authorPHID;
protected $viewPolicy;
protected $editPolicy;
protected $commentPHID;
protected $commentVersion = 0;
protected $transactionType;
protected $oldValue;
protected $newValue;
protected $metadata = array();
protected $contentSource;
private $comment;
private $commentNotLoaded;
private $handles;
private $renderingTarget = self::TARGET_HTML;
private $transactionGroup = array();
private $viewer = self::ATTACHABLE;
private $object = self::ATTACHABLE;
private $oldValueHasBeenSet = false;
private $ignoreOnNoEffect;
/**
* Flag this transaction as a pure side-effect which should be ignored when
* applying transactions if it has no effect, even if transaction application
* would normally fail. This both provides users with better error messages
* and allows transactions to perform optional side effects.
*/
public function setIgnoreOnNoEffect($ignore) {
$this->ignoreOnNoEffect = $ignore;
return $this;
}
public function getIgnoreOnNoEffect() {
return $this->ignoreOnNoEffect;
}
public function shouldGenerateOldValue() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
return false;
}
return true;
}
abstract public function getApplicationTransactionType();
private function getApplicationObjectTypeName() {
$types = PhabricatorPHIDType::getAllTypes();
$type = idx($types, $this->getApplicationTransactionType());
if ($type) {
return $type->getTypeName();
}
return pht('Object');
}
public function getApplicationTransactionCommentObject() {
throw new Exception("Not implemented!");
}
public function getApplicationTransactionViewObject() {
return new PhabricatorApplicationTransactionView();
}
public function getMetadataValue($key, $default = null) {
return idx($this->metadata, $key, $default);
}
public function setMetadataValue($key, $value) {
$this->metadata[$key] = $value;
return $this;
}
public function generatePHID() {
$type = PhabricatorApplicationTransactionPHIDTypeTransaction::TYPECONST;
$subtype = $this->getApplicationTransactionType();
return PhabricatorPHID::generateNewPHID($type, $subtype);
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'oldValue' => self::SERIALIZATION_JSON,
'newValue' => self::SERIALIZATION_JSON,
'metadata' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function setContentSource(PhabricatorContentSource $content_source) {
$this->contentSource = $content_source->serialize();
return $this;
}
public function getContentSource() {
return PhabricatorContentSource::newFromSerialized($this->contentSource);
}
public function hasComment() {
return $this->getComment() && strlen($this->getComment()->getContent());
}
public function getComment() {
if ($this->commentNotLoaded) {
throw new Exception("Comment for this transaction was not loaded.");
}
return $this->comment;
}
public function attachComment(
PhabricatorApplicationTransactionComment $comment) {
$this->comment = $comment;
$this->commentNotLoaded = false;
return $this;
}
public function setCommentNotLoaded($not_loaded) {
$this->commentNotLoaded = $not_loaded;
return $this;
}
public function attachObject($object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->assertAttached($this->object);
}
public function getRemarkupBlocks() {
$blocks = array();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
$custom_blocks = $field->getApplicationTransactionRemarkupBlocks(
$this);
foreach ($custom_blocks as $custom_block) {
$blocks[] = $custom_block;
}
}
break;
}
if ($this->getComment()) {
$blocks[] = $this->getComment()->getContent();
}
return $blocks;
}
public function setOldValue($value) {
$this->oldValueHasBeenSet = true;
$this->writeField('oldValue', $value);
return $this;
}
public function hasOldValue() {
return $this->oldValueHasBeenSet;
}
/* -( Rendering )---------------------------------------------------------- */
public function setRenderingTarget($rendering_target) {
$this->renderingTarget = $rendering_target;
return $this;
}
public function getRenderingTarget() {
return $this->renderingTarget;
}
public function attachViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->assertAttached($this->viewer);
}
public function getRequiredHandlePHIDs() {
$phids = array();
$old = $this->getOldValue();
$new = $this->getNewValue();
$phids[] = array($this->getAuthorPHID());
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
$phids[] = $field->getApplicationTransactionRequiredHandlePHIDs(
$this);
}
break;
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$phids[] = $old;
$phids[] = $new;
break;
case PhabricatorTransactions::TYPE_EDGE:
$phids[] = ipull($old, 'dst');
$phids[] = ipull($new, 'dst');
break;
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
if (!PhabricatorPolicyQuery::isGlobalPolicy($old)) {
$phids[] = array($old);
}
if (!PhabricatorPolicyQuery::isGlobalPolicy($new)) {
$phids[] = array($new);
}
break;
}
return array_mergev($phids);
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function getHandle($phid) {
if (empty($this->handles[$phid])) {
throw new Exception(
pht(
'Transaction ("%s") requires a handle ("%s") that it did not '.
'load.',
$this->getPHID(),
$phid));
}
return $this->handles[$phid];
}
public function getHandleIfExists($phid) {
return idx($this->handles, $phid);
}
public function getHandles() {
if ($this->handles === null) {
throw new Exception(
'Transaction requires handles and it did not load them.'
);
}
return $this->handles;
}
public function renderHandleLink($phid) {
if ($this->renderingTarget == self::TARGET_HTML) {
return $this->getHandle($phid)->renderLink();
} else {
return $this->getHandle($phid)->getLinkName();
}
}
public function renderHandleList(array $phids) {
$links = array();
foreach ($phids as $phid) {
$links[] = $this->renderHandleLink($phid);
}
if ($this->renderingTarget == self::TARGET_HTML) {
return phutil_implode_html(', ', $links);
} else {
return implode(', ', $links);
}
}
public function renderPolicyName($phid) {
$policy = PhabricatorPolicy::newFromPolicyAndHandle(
$phid,
$this->getHandleIfExists($phid));
if ($this->renderingTarget == self::TARGET_HTML) {
$output = $policy->renderDescription();
} else {
$output = hsprintf('%s', $policy->getFullName());
}
return $output;
}
public function getIcon() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return 'comment';
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return 'message';
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return 'lock';
case PhabricatorTransactions::TYPE_EDGE:
return 'link';
}
return 'edit';
}
public function getColor() {
return null;
}
protected function getTransactionCustomField() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$key = $this->getMetadataValue('customfield:key');
if (!$key) {
return null;
}
$field = PhabricatorCustomField::getObjectField(
$this->getObject(),
PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS,
$key);
if (!$field) {
return null;
}
$field->setViewer($this->getViewer());
return $field;
}
return null;
}
public function shouldHide() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
if ($this->getOldValue() === null) {
return true;
} else {
return false;
}
break;
+ case PhabricatorTransactions::TYPE_CUSTOMFIELD:
+ $field = $this->getTransactionCustomField();
+ if ($field) {
+ return $field->shouldHideInApplicationTransactions($this);
+ }
}
return false;
}
public function shouldHideForMail(array $xactions) {
return $this->shouldHide();
}
public function shouldHideForFeed() {
return $this->shouldHide();
}
public function getTitleForMail() {
return id(clone $this)->setRenderingTarget('text')->getTitle();
}
public function getBodyForMail() {
$comment = $this->getComment();
if ($comment && strlen($comment->getContent())) {
return $comment->getContent();
}
return null;
}
public function getNoEffectDescription() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return pht('You can not post an empty comment.');
case PhabricatorTransactions::TYPE_VIEW_POLICY:
return pht(
'This %s already has that view policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return pht(
'This %s already has that edit policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht(
'This %s already has that join policy.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht(
'All users are already subscribed to this %s.',
$this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_EDGE:
return pht('Edges already exist; transaction has no effect.');
}
return pht('Transaction has no effect.');
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return pht(
'%s added a comment.',
$this->renderHandleLink($author_phid));
case PhabricatorTransactions::TYPE_VIEW_POLICY:
return pht(
'%s changed the visibility of this %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName(),
$this->renderPolicyName($old),
$this->renderPolicyName($new));
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return pht(
'%s changed the edit policy of this %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName(),
$this->renderPolicyName($old),
$this->renderPolicyName($new));
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht(
'%s changed the join policy of this %s from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName(),
$this->renderPolicyName($old),
$this->renderPolicyName($new));
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ($add && $rem) {
return pht(
'%s edited subscriber(s), added %d: %s; removed %d: %s.',
$this->renderHandleLink($author_phid),
count($add),
$this->renderHandleList($add),
count($rem),
$this->renderHandleList($rem));
} else if ($add) {
return pht(
'%s added %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($add),
$this->renderHandleList($add));
} else if ($rem) {
return pht(
'%s removed %d subscriber(s): %s.',
$this->renderHandleLink($author_phid),
count($rem),
$this->renderHandleList($rem));
} else {
// This is used when rendering previews, before the user actually
// selects any CCs.
return pht(
'%s updated subscribers...',
$this->renderHandleLink($author_phid));
}
break;
case PhabricatorTransactions::TYPE_EDGE:
$new = ipull($new, 'dst');
$old = ipull($old, 'dst');
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
$type = $this->getMetadata('edge:type');
$type = head($type);
if ($add && $rem) {
$string = PhabricatorEdgeConfig::getEditStringForEdgeType($type);
return pht(
$string,
$this->renderHandleLink($author_phid),
count($add),
$this->renderHandleList($add),
count($rem),
$this->renderHandleList($rem));
} else if ($add) {
$string = PhabricatorEdgeConfig::getAddStringForEdgeType($type);
return pht(
$string,
$this->renderHandleLink($author_phid),
count($add),
$this->renderHandleList($add));
} else if ($rem) {
$string = PhabricatorEdgeConfig::getRemoveStringForEdgeType($type);
return pht(
$string,
$this->renderHandleLink($author_phid),
count($rem),
$this->renderHandleList($rem));
} else {
return pht(
'%s edited edge metadata.',
$this->renderHandleLink($author_phid));
}
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionTitle($this);
} else {
return pht(
'%s edited a custom field.',
$this->renderHandleLink($author_phid));
}
default:
return pht(
'%s edited this %s.',
$this->renderHandleLink($author_phid),
$this->getApplicationObjectTypeName());
}
}
public function getTitleForFeed(PhabricatorFeedStory $story) {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return pht(
'%s added a comment to %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_VIEW_POLICY:
return pht(
'%s changed the visibility for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return pht(
'%s changed the edit policy for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht(
'%s changed the join policy for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht(
'%s updated subscribers of %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_EDGE:
$type = $this->getMetadata('edge:type');
$type = head($type);
$string = PhabricatorEdgeConfig::getFeedStringForEdgeType($type);
return pht(
$string,
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionTitleForFeed($this, $story);
} else {
return pht(
'%s edited a custom field on %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
}
return $this->getTitle();
}
public function getBodyForFeed(PhabricatorFeedStory $story) {
$old = $this->getOldValue();
$new = $this->getNewValue();
$body = null;
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
$text = $this->getComment()->getContent();
$body = phutil_escape_html_newlines(
phutil_utf8_shorten($text, 128));
break;
}
return $body;
}
public function getActionStrength() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return 0.5;
}
return 1.0;
}
public function isCommentTransaction() {
if ($this->hasComment()) {
return true;
}
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return true;
}
return false;
}
public function getActionName() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
return pht('Commented On');
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
return pht('Changed Policy');
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht('Changed Subscribers');
default:
return pht('Updated');
}
}
public function getMailTags() {
return array();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionHasChangeDetails($this);
}
break;
}
return false;
}
public function renderChangeDetails(PhabricatorUser $viewer) {
switch ($this->getTransactionType()) {
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getTransactionCustomField();
if ($field) {
return $field->getApplicationTransactionChangeDetails($this, $viewer);
}
break;
}
return $this->renderTextCorpusChangeDetails();
}
public function renderTextCorpusChangeDetails(
PhabricatorUser $viewer,
$old,
$new) {
require_celerity_resource('differential-changeset-view-css');
$view = id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setUser($viewer)
->setOldText($old)
->setNewText($new);
return $view->render();
}
public function attachTransactionGroup(array $group) {
assert_instances_of($group, 'PhabricatorApplicationTransaction');
$this->transactionGroup = $group;
return $this;
}
public function getTransactionGroup() {
return $this->transactionGroup;
}
/**
* Should this transaction be visually grouped with an existing transaction
* group?
*
* @param list<PhabricatorApplicationTransaction> List of transactions.
* @return bool True to display in a group with the other transactions.
*/
public function shouldDisplayGroupWith(array $group) {
$this_source = null;
if ($this->getContentSource()) {
$this_source = $this->getContentSource()->getSource();
}
foreach ($group as $xaction) {
// Don't group transactions by different authors.
if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) {
return false;
}
// Don't group transactions for different objects.
if ($xaction->getObjectPHID() != $this->getObjectPHID()) {
return false;
}
// Don't group anything into a group which already has a comment.
if ($xaction->isCommentTransaction()) {
return false;
}
// Don't group transactions from different content sources.
$other_source = null;
if ($xaction->getContentSource()) {
$other_source = $xaction->getContentSource()->getSource();
}
if ($other_source != $this_source) {
return false;
}
// Don't group transactions which happened more than 2 minutes apart.
$apart = abs($xaction->getDateCreated() - $this->getDateCreated());
if ($apart > (60 * 2)) {
return false;
}
}
return true;
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
}
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() == $this->getAuthorPHID());
}
public function describeAutomaticCapability($capability) {
// TODO: (T603) Exact policies are unclear here.
return null;
}
}
diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php
index 162eda8b29..38c8843ec5 100644
--- a/src/infrastructure/customfield/field/PhabricatorCustomField.php
+++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php
@@ -1,1162 +1,1170 @@
<?php
/**
* @task apps Building Applications with Custom Fields
* @task core Core Properties and Field Identity
* @task proxy Field Proxies
* @task context Contextual Data
* @task storage Field Storage
* @task edit Integration with Edit Views
* @task view Integration with Property Views
* @task list Integration with List views
* @task appsearch Integration with ApplicationSearch
* @task appxaction Integration with ApplicationTransactions
* @task globalsearch Integration with Global Search
*/
abstract class PhabricatorCustomField {
private $viewer;
private $object;
private $proxy;
const ROLE_APPLICATIONTRANSACTIONS = 'ApplicationTransactions';
const ROLE_APPLICATIONSEARCH = 'ApplicationSearch';
const ROLE_STORAGE = 'storage';
const ROLE_DEFAULT = 'default';
const ROLE_EDIT = 'edit';
const ROLE_VIEW = 'view';
const ROLE_LIST = 'list';
const ROLE_GLOBALSEARCH = 'GlobalSearch';
/* -( Building Applications with Custom Fields )--------------------------- */
/**
* @task apps
*/
public static function getObjectFields(
PhabricatorCustomFieldInterface $object,
$role) {
try {
$attachment = $object->getCustomFields();
} catch (PhabricatorDataNotAttachedException $ex) {
$attachment = new PhabricatorCustomFieldAttachment();
$object->attachCustomFields($attachment);
}
try {
$field_list = $attachment->getCustomFieldList($role);
} catch (PhabricatorCustomFieldNotAttachedException $ex) {
$base_class = $object->getCustomFieldBaseClass();
$spec = $object->getCustomFieldSpecificationForRole($role);
if (!is_array($spec)) {
$obj_class = get_class($object);
throw new Exception(
"Expected an array from getCustomFieldSpecificationForRole() for ".
"object of class '{$obj_class}'.");
}
$fields = PhabricatorCustomField::buildFieldList($base_class, $spec);
foreach ($fields as $key => $field) {
if (!$field->shouldEnableForRole($role)) {
unset($fields[$key]);
}
}
foreach ($fields as $field) {
$field->setObject($object);
}
$field_list = new PhabricatorCustomFieldList($fields);
$attachment->addCustomFieldList($role, $field_list);
}
return $field_list;
}
/**
* @task apps
*/
public static function getObjectField(
PhabricatorCustomFieldInterface $object,
$role,
$field_key) {
$fields = self::getObjectFields($object, $role)->getFields();
return idx($fields, $field_key);
}
/**
* @task apps
*/
public static function buildFieldList($base_class, array $spec) {
$field_objects = id(new PhutilSymbolLoader())
->setAncestorClass($base_class)
->loadObjects();
$fields = array();
$from_map = array();
foreach ($field_objects as $field_object) {
$current_class = get_class($field_object);
foreach ($field_object->createFields() as $field) {
$key = $field->getFieldKey();
if (isset($fields[$key])) {
$original_class = $from_map[$key];
throw new Exception(
"Both '{$original_class}' and '{$current_class}' define a custom ".
"field with field key '{$key}'. Field keys must be unique.");
}
$from_map[$key] = $current_class;
$fields[$key] = $field;
}
}
foreach ($fields as $key => $field) {
if (!$field->isFieldEnabled()) {
unset($fields[$key]);
}
}
$fields = array_select_keys($fields, array_keys($spec)) + $fields;
foreach ($spec as $key => $config) {
if (empty($fields[$key])) {
continue;
}
if (!empty($config['disabled'])) {
if ($fields[$key]->canDisableField()) {
unset($fields[$key]);
}
}
}
return $fields;
}
/* -( Core Properties and Field Identity )--------------------------------- */
/**
* Return a key which uniquely identifies this field, like
* "mycompany:dinosaur:count". Normally you should provide some level of
* namespacing to prevent collisions.
*
* @return string String which uniquely identifies this field.
* @task core
*/
public function getFieldKey() {
if ($this->proxy) {
return $this->proxy->getFieldKey();
}
throw new PhabricatorCustomFieldImplementationIncompleteException(
$this,
$field_key_is_incomplete = true);
}
/**
* Return a human-readable field name.
*
* @return string Human readable field name.
* @task core
*/
public function getFieldName() {
if ($this->proxy) {
return $this->proxy->getFieldName();
}
return $this->getFieldKey();
}
/**
* Return a short, human-readable description of the field's behavior. This
* provides more context to administrators when they are customizing fields.
*
* @return string|null Optional human-readable description.
* @task core
*/
public function getFieldDescription() {
if ($this->proxy) {
return $this->proxy->getFieldDescription();
}
return null;
}
/**
* Most field implementations are unique, in that one class corresponds to
* one field. However, some field implementations are general and a single
* implementation may drive several fields.
*
* For general implementations, the general field implementation can return
* multiple field instances here.
*
* @return list<PhabricatorCustomField> List of fields.
* @task core
*/
public function createFields() {
return array($this);
}
/**
* You can return `false` here if the field should not be enabled for any
* role. For example, it might depend on something (like an application or
* library) which isn't installed, or might have some global configuration
* which allows it to be disabled.
*
* @return bool False to completely disable this field for all roles.
* @task core
*/
public function isFieldEnabled() {
if ($this->proxy) {
return $this->proxy->isFieldEnabled();
}
return true;
}
/**
* Low level selector for field availability. Fields can appear in different
* roles (like an edit view, a list view, etc.), but not every field needs
* to appear everywhere. Fields that are disabled in a role won't appear in
* that context within applications.
*
* Normally, you do not need to override this method. Instead, override the
* methods specific to roles you want to enable. For example, implement
* @{method:shouldUseStorage()} to activate the `'storage'` role.
*
* @return bool True to enable the field for the given role.
* @task core
*/
public function shouldEnableForRole($role) {
if ($this->proxy) {
return $this->proxy->shouldEnableForRole($role);
}
switch ($role) {
case self::ROLE_APPLICATIONTRANSACTIONS:
return $this->shouldAppearInApplicationTransactions();
case self::ROLE_APPLICATIONSEARCH:
return $this->shouldAppearInApplicationSearch();
case self::ROLE_STORAGE:
return $this->shouldUseStorage();
case self::ROLE_EDIT:
return $this->shouldAppearInEditView();
case self::ROLE_VIEW:
return $this->shouldAppearInPropertyView();
case self::ROLE_LIST:
return $this->shouldAppearInListView();
case self::ROLE_GLOBALSEARCH:
return $this->shouldAppearInGlobalSearch();
case self::ROLE_DEFAULT:
return true;
default:
throw new Exception("Unknown field role '{$role}'!");
}
}
/**
* Allow administrators to disable this field. Most fields should allow this,
* but some are fundamental to the behavior of the application and can be
* locked down to avoid chaos, disorder, and the decline of civilization.
*
* @return bool False to prevent this field from being disabled through
* configuration.
* @task core
*/
public function canDisableField() {
return true;
}
public function shouldDisableByDefault() {
return false;
}
/**
* Return an index string which uniquely identifies this field.
*
* @return string Index string which uniquely identifies this field.
* @task core
*/
final public function getFieldIndex() {
return PhabricatorHash::digestForIndex($this->getFieldKey());
}
/* -( Field Proxies )------------------------------------------------------ */
/**
* Proxies allow a field to use some other field's implementation for most
* of their behavior while still subclassing an application field. When a
* proxy is set for a field with @{method:setProxy}, all of its methods will
* call through to the proxy by default.
*
* This is most commonly used to implement configuration-driven custom fields
* using @{class:PhabricatorStandardCustomField}.
*
* This method must be overridden to return `true` before a field can accept
* proxies.
*
* @return bool True if you can @{method:setProxy} this field.
* @task proxy
*/
public function canSetProxy() {
if ($this instanceof PhabricatorStandardCustomFieldInterface) {
return true;
}
return false;
}
/**
* Set the proxy implementation for this field. See @{method:canSetProxy} for
* discussion of field proxies.
*
* @param PhabricatorCustomField Field implementation.
* @return this
*/
final public function setProxy(PhabricatorCustomField $proxy) {
if (!$this->canSetProxy()) {
throw new PhabricatorCustomFieldNotProxyException($this);
}
$this->proxy = $proxy;
return $this;
}
/**
* Get the field's proxy implementation, if any. For discussion, see
* @{method:canSetProxy}.
*
* @return PhabricatorCustomField|null Proxy field, if one is set.
*/
final public function getProxy() {
return $this->proxy;
}
/* -( Contextual Data )---------------------------------------------------- */
/**
* Sets the object this field belongs to.
*
* @param PhabricatorCustomFieldInterface The object this field belongs to.
* @return this
* @task context
*/
final public function setObject(PhabricatorCustomFieldInterface $object) {
if ($this->proxy) {
$this->proxy->setObject($object);
return $this;
}
$this->object = $object;
$this->didSetObject($object);
return $this;
}
/**
* Read object data into local field storage, if applicable.
*
* @param PhabricatorCustomFieldInterface The object this field belongs to.
* @return this
* @task context
*/
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
if ($this->proxy) {
$this->proxy->readValueFromObject($object);
}
return $this;
}
/**
* Get the object this field belongs to.
*
* @return PhabricatorCustomFieldInterface The object this field belongs to.
* @task context
*/
final public function getObject() {
if ($this->proxy) {
return $this->proxy->getObject();
}
return $this->object;
}
/**
* This is a hook, primarily for subclasses to load object data.
*
* @return PhabricatorCustomFieldInterface The object this field belongs to.
* @return void
*/
protected function didSetObject(PhabricatorCustomFieldInterface $object) {
return;
}
/**
* @task context
*/
final public function setViewer(PhabricatorUser $viewer) {
if ($this->proxy) {
$this->proxy->setViewer($viewer);
return $this;
}
$this->viewer = $viewer;
return $this;
}
/**
* @task context
*/
final public function getViewer() {
if ($this->proxy) {
return $this->proxy->getViewer();
}
return $this->viewer;
}
/**
* @task context
*/
final protected function requireViewer() {
if ($this->proxy) {
return $this->proxy->requireViewer();
}
if (!$this->viewer) {
throw new PhabricatorCustomFieldDataNotAvailableException($this);
}
return $this->viewer;
}
/* -( Storage )------------------------------------------------------------ */
/**
* Return true to use field storage.
*
* Fields which can be edited by the user will most commonly use storage,
* while some other types of fields (for instance, those which just display
* information in some stylized way) may not. Many builtin fields do not use
* storage because their data is available on the object itself.
*
* If you implement this, you must also implement @{method:getValueForStorage}
* and @{method:setValueFromStorage}.
*
* @return bool True to use storage.
* @task storage
*/
public function shouldUseStorage() {
if ($this->proxy) {
return $this->proxy->shouldUseStorage();
}
return false;
}
/**
* Return a new, empty storage object. This should be a subclass of
* @{class:PhabricatorCustomFieldStorage} which is bound to the application's
* database.
*
* @return PhabricatorCustomFieldStorage New empty storage object.
* @task storage
*/
public function newStorageObject() {
if ($this->proxy) {
return $this->proxy->newStorageObject();
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Return a serialized representation of the field value, appropriate for
* storing in auxiliary field storage. You must implement this method if
* you implement @{method:shouldUseStorage}.
*
* If the field value is a scalar, it can be returned unmodiifed. If not,
* it should be serialized (for example, using JSON).
*
* @return string Serialized field value.
* @task storage
*/
public function getValueForStorage() {
if ($this->proxy) {
return $this->proxy->getValueForStorage();
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* 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
* null. You must implement this method if you implement
* @{method:shouldUseStorage}.
*
* Usually, the value can be loaded directly. If it isn't a scalar, you'll
* need to undo whatever serialization you applied in
* @{method:getValueForStorage}.
*
* @param string|null Serialized field representation (from
* @{method:getValueForStorage}) or null if no value has
* ever been stored.
* @return this
* @task storage
*/
public function setValueFromStorage($value) {
if ($this->proxy) {
return $this->proxy->setValueFromStorage($value);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/* -( ApplicationSearch )-------------------------------------------------- */
/**
* Appearing in ApplicationSearch allows a field to be indexed and searched
* for.
*
* @return bool True to appear in ApplicationSearch.
* @task appsearch
*/
public function shouldAppearInApplicationSearch() {
if ($this->proxy) {
return $this->proxy->shouldAppearInApplicationSearch();
}
return false;
}
/**
* Return one or more indexes which this field can meaningfully query against
* to implement ApplicationSearch.
*
* Normally, you should build these using @{method:newStringIndex} and
* @{method:newNumericIndex}. For example, if a field holds a numeric value
* it might return a single numeric index:
*
* return array($this->newNumericIndex($this->getValue()));
*
* If a field holds a more complex value (like a list of users), it might
* return several string indexes:
*
* $indexes = array();
* foreach ($this->getValue() as $phid) {
* $indexes[] = $this->newStringIndex($phid);
* }
* return $indexes;
*
* @return list<PhabricatorCustomFieldIndexStorage> List of indexes.
* @task appsearch
*/
public function buildFieldIndexes() {
if ($this->proxy) {
return $this->proxy->buildFieldIndexes();
}
return array();
}
/**
* Build a new empty storage object for storing string indexes. Normally,
* this should be a concrete subclass of
* @{class:PhabricatorCustomFieldStringIndexStorage}.
*
* @return PhabricatorCustomFieldStringIndexStorage Storage object.
* @task appsearch
*/
protected function newStringIndexStorage() {
// NOTE: This intentionally isn't proxied, to avoid call cycles.
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Build a new empty storage object for storing string indexes. Normally,
* this should be a concrete subclass of
* @{class:PhabricatorCustomFieldStringIndexStorage}.
*
* @return PhabricatorCustomFieldStringIndexStorage Storage object.
* @task appsearch
*/
protected function newNumericIndexStorage() {
// NOTE: This intentionally isn't proxied, to avoid call cycles.
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Build and populate storage for a string index.
*
* @param string String to index.
* @return PhabricatorCustomFieldStringIndexStorage Populated storage.
* @task appsearch
*/
protected function newStringIndex($value) {
if ($this->proxy) {
return $this->proxy->newStringIndex();
}
$key = $this->getFieldIndex();
return $this->newStringIndexStorage()
->setIndexKey($key)
->setIndexValue($value);
}
/**
* Build and populate storage for a numeric index.
*
* @param string Numeric value to index.
* @return PhabricatorCustomFieldNumericIndexStorage Populated storage.
* @task appsearch
*/
protected function newNumericIndex($value) {
if ($this->proxy) {
return $this->proxy->newNumericIndex();
}
$key = $this->getFieldIndex();
return $this->newNumericIndexStorage()
->setIndexKey($key)
->setIndexValue($value);
}
/**
* Read a query value from a request, for storage in a saved query. Normally,
* this method should, e.g., read a string out of the request.
*
* @param PhabricatorApplicationSearchEngine Engine building the query.
* @param AphrontRequest Request to read from.
* @return wild
* @task appsearch
*/
public function readApplicationSearchValueFromRequest(
PhabricatorApplicationSearchEngine $engine,
AphrontRequest $request) {
if ($this->proxy) {
return $this->proxy->readApplicationSearchValueFromRequest(
$engine,
$request);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Constrain a query, given a field value. Generally, this method should
* use `with...()` methods to apply filters or other constraints to the
* query.
*
* @param PhabricatorApplicationSearchEngine Engine executing the query.
* @param PhabricatorCursorPagedPolicyAwareQuery Query to constrain.
* @param wild Constraint provided by the user.
* @return void
* @task appsearch
*/
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
if ($this->proxy) {
return $this->proxy->applyApplicationSearchConstraintToQuery(
$engine,
$query,
$value);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Append search controls to the interface. If you need handles, use
* @{method:getRequiredHandlePHIDsForApplicationSearch} to get them.
*
* @param PhabricatorApplicationSearchEngine Engine constructing the form.
* @param AphrontFormView The form to update.
* @param wild Value from the saved query.
* @param list<PhabricatorObjectHandle> List of handles.
* @return void
* @task appsearch
*/
public function appendToApplicationSearchForm(
PhabricatorApplicationSearchEngine $engine,
AphrontFormView $form,
$value,
array $handles) {
if ($this->proxy) {
return $this->proxy->appendToApplicationSearchForm(
$engine,
$form,
$value,
$handles);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* Return a list of PHIDs which @{method:appendToApplicationSearchForm} needs
* handles for. This is primarily useful if the field stores PHIDs and you
* need to (for example) render a tokenizer control.
*
* @param wild Value from the saved query.
* @return list<phid> List of PHIDs.
* @task appsearch
*/
public function getRequiredHandlePHIDsForApplicationSearch($value) {
if ($this->proxy) {
return $this->proxy->getRequiredHandlePHIDsForApplicationSearch($value);
}
return array();
}
/* -( ApplicationTransactions )-------------------------------------------- */
/**
* Appearing in ApplicationTrasactions allows a field to be edited using
* standard workflows.
*
* @return bool True to appear in ApplicationTransactions.
* @task appxaction
*/
public function shouldAppearInApplicationTransactions() {
if ($this->proxy) {
return $this->proxy->shouldAppearInApplicationTransactions();
}
return false;
}
/**
* @task appxaction
*/
public function getApplicationTransactionType() {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionType();
}
return PhabricatorTransactions::TYPE_CUSTOMFIELD;
}
/**
* @task appxaction
*/
public function getApplicationTransactionMetadata() {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionMetadata();
}
return array();
}
/**
* @task appxaction
*/
public function getOldValueForApplicationTransactions() {
if ($this->proxy) {
return $this->proxy->getOldValueForApplicationTransactions();
}
return $this->getValueForStorage();
}
/**
* @task appxaction
*/
public function getNewValueForApplicationTransactions() {
if ($this->proxy) {
return $this->proxy->getNewValueForApplicationTransactions();
}
return $this->getValueForStorage();
}
/**
* @task appxaction
*/
public function setValueFromApplicationTransactions($value) {
if ($this->proxy) {
return $this->proxy->setValueFromApplicationTransactions($value);
}
return $this->setValueFromStorage($value);
}
/**
* @task appxaction
*/
public function getNewValueFromApplicationTransactions(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getNewValueFromApplicationTransactions($xaction);
}
return $xaction->getNewValue();
}
/**
* @task appxaction
*/
public function getApplicationTransactionHasEffect(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionHasEffect($xaction);
}
return ($xaction->getOldValue() !== $xaction->getNewValue());
}
/**
* @task appxaction
*/
public function applyApplicationTransactionInternalEffects(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->applyApplicationTransactionInternalEffects($xaction);
}
return;
}
/**
* @task appxaction
*/
public function getApplicationTransactionRemarkupBlocks(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionRemarkupBlocks($xaction);
}
return array();
}
/**
* @task appxaction
*/
public function applyApplicationTransactionExternalEffects(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->applyApplicationTransactionExternalEffects($xaction);
}
if (!$this->shouldEnableForRole(self::ROLE_STORAGE)) {
return;
}
$this->setValueFromApplicationTransactions($xaction->getNewValue());
$value = $this->getValueForStorage();
$table = $this->newStorageObject();
$conn_w = $table->establishConnection('w');
if ($value === null) {
queryfx(
$conn_w,
'DELETE FROM %T WHERE objectPHID = %s AND fieldIndex = %s',
$table->getTableName(),
$this->getObject()->getPHID(),
$this->getFieldIndex());
} else {
queryfx(
$conn_w,
'INSERT INTO %T (objectPHID, fieldIndex, fieldValue)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE fieldValue = VALUES(fieldValue)',
$table->getTableName(),
$this->getObject()->getPHID(),
$this->getFieldIndex(),
$value);
}
return;
}
/**
* Validate transactions for an object. This allows you to raise an error
* when a transaction would set a field to an invalid value, or when a field
* is required but no transactions provide value.
*
* @param PhabricatorLiskDAO Editor applying the transactions.
* @param string Transaction type. This type is always
* `PhabricatorTransactions::TYPE_CUSTOMFIELD`, it is provided for
* convenience when constructing exceptions.
* @param list<PhabricatorApplicationTransaction> Transactions being applied,
* which may be empty if this field is not being edited.
* @return list<PhabricatorApplicationTransactionValidationError> Validation
* errors.
*
* @task appxaction
*/
public function validateApplicationTransactions(
PhabricatorApplicationTransactionEditor $editor,
$type,
array $xactions) {
if ($this->proxy) {
return $this->proxy->validateApplicationTransactions(
$editor,
$type,
$xactions);
}
return array();
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionTitle(
$xaction);
}
$author_phid = $xaction->getAuthorPHID();
return pht(
'%s updated this object.',
$xaction->renderHandleLink($author_phid));
}
public function getApplicationTransactionTitleForFeed(
PhabricatorApplicationTransaction $xaction,
PhabricatorFeedStory $story) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionTitleForFeed(
$xaction,
$story);
}
$author_phid = $xaction->getAuthorPHID();
$object_phid = $xaction->getObjectPHID();
return pht(
'%s updated %s.',
$xaction->renderHandleLink($author_phid),
$xaction->renderHandleLink($object_phid));
}
public function getApplicationTransactionHasChangeDetails(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionHasChangeDetails(
$xaction);
}
return false;
}
public function getApplicationTransactionChangeDetails(
PhabricatorApplicationTransaction $xaction,
PhabricatorUser $viewer) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionChangeDetails(
$xaction,
$viewer);
}
return null;
}
public function getApplicationTransactionRequiredHandlePHIDs(
PhabricatorApplicationTransaction $xaction) {
if ($this->proxy) {
return $this->proxy->getApplicationTransactionRequiredHandlePHIDs(
$xaction);
}
return array();
}
+ public function shouldHideInApplicationTransactions(
+ PhabricatorApplicationTransaction $xaction) {
+ if ($this->proxy) {
+ return $this->proxy->shouldHideInApplicationTransactions($xaction);
+ }
+ return false;
+ }
+
/* -( Edit View )---------------------------------------------------------- */
/**
* @task edit
*/
public function shouldAppearInEditView() {
if ($this->proxy) {
return $this->proxy->shouldAppearInEditView();
}
return false;
}
/**
* @task edit
*/
public function readValueFromRequest(AphrontRequest $request) {
if ($this->proxy) {
return $this->proxy->readValueFromRequest($request);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* @task edit
*/
public function getRequiredHandlePHIDsForEdit() {
if ($this->proxy) {
return $this->proxy->getRequiredHandlePHIDsForEdit();
}
return array();
}
/**
* @task edit
*/
public function renderEditControl(array $handles) {
if ($this->proxy) {
return $this->proxy->renderEditControl($handles);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/* -( Property View )------------------------------------------------------ */
/**
* @task view
*/
public function shouldAppearInPropertyView() {
if ($this->proxy) {
return $this->proxy->shouldAppearInPropertyView();
}
return false;
}
/**
* @task view
*/
public function renderPropertyViewLabel() {
if ($this->proxy) {
return $this->proxy->renderPropertyViewLabel();
}
return $this->getFieldName();
}
/**
* @task view
*/
public function renderPropertyViewValue(array $handles) {
if ($this->proxy) {
return $this->proxy->renderPropertyViewValue($handles);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* @task view
*/
public function getStyleForPropertyView() {
if ($this->proxy) {
return $this->proxy->getStyleForPropertyView();
}
return 'property';
}
/**
* @task view
*/
public function getIconForPropertyView() {
if ($this->proxy) {
return $this->proxy->getIconForPropertyView();
}
return null;
}
/**
* @task view
*/
public function getRequiredHandlePHIDsForPropertyView() {
if ($this->proxy) {
return $this->proxy->getRequiredHandlePHIDsForPropertyView();
}
return array();
}
/* -( List View )---------------------------------------------------------- */
/**
* @task list
*/
public function shouldAppearInListView() {
if ($this->proxy) {
return $this->proxy->shouldAppearInListView();
}
return false;
}
/**
* @task list
*/
public function renderOnListItem(PHUIObjectItemView $view) {
if ($this->proxy) {
return $this->proxy->renderOnListItem($view);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/* -( Global Search )------------------------------------------------------ */
/**
* @task globalsearch
*/
public function shouldAppearInGlobalSearch() {
if ($this->proxy) {
return $this->proxy->shouldAppearInGlobalSearch();
}
return false;
}
/**
* @task globalsearch
*/
public function updateAbstractDocument(
PhabricatorSearchAbstractDocument $document) {
if ($this->proxy) {
return $this->proxy->updateAbstractDocument($document);
}
return $document;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Sep 7, 10:08 AM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
223035
Default Alt Text
(94 KB)

Event Timeline