Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/customfield/DifferentialCustomField.php b/src/applications/differential/customfield/DifferentialCustomField.php
index b4ef47c274..f80798d0e6 100644
--- a/src/applications/differential/customfield/DifferentialCustomField.php
+++ b/src/applications/differential/customfield/DifferentialCustomField.php
@@ -1,251 +1,251 @@
<?php
/**
* @task commitmessage Integration with Commit Messages
* @task diff Integration with Diff Properties
*/
abstract class DifferentialCustomField
extends PhabricatorCustomField {
const ROLE_COMMITMESSAGE = 'differential:commitmessage';
const ROLE_COMMITMESSAGEEDIT = 'differential:commitmessageedit';
/**
* TODO: It would be nice to remove this, but a lot of different code is
* bound together by it. Until everything is modernized, retaining the old
* field keys is the only reasonable way to update things one piece
* at a time.
*/
public function getFieldKeyForConduit() {
return $this->getFieldKey();
}
public function shouldEnableForRole($role) {
switch ($role) {
case self::ROLE_COMMITMESSAGE:
return $this->shouldAppearInCommitMessage();
case self::ROLE_COMMITMESSAGEEDIT:
return $this->shouldAppearInCommitMessage() &&
$this->shouldAllowEditInCommitMessage();
}
return parent::shouldEnableForRole($role);
}
protected function parseObjectList(
$value,
array $types,
$allow_partial = false) {
return id(new PhabricatorObjectListQuery())
->setViewer($this->getViewer())
->setAllowedTypes($types)
->setObjectList($value)
->setAllowPartialResults($allow_partial)
->execute();
}
protected function renderObjectList(array $handles) {
if (!$handles) {
return null;
}
$out = array();
foreach ($handles as $handle) {
if ($handle->getPolicyFiltered()) {
$out[] = $handle->getPHID();
} else if ($handle->isComplete()) {
- $out[] = $handle->getObjectName();
+ $out[] = $handle->getCommandLineObjectName();
}
}
return implode(', ', $out);
}
public function getWarningsForDetailView() {
if ($this->getProxy()) {
return $this->getProxy()->getWarningsForDetailView();
}
return array();
}
public function getRequiredHandlePHIDsForRevisionHeaderWarnings() {
return array();
}
public function getWarningsForRevisionHeader(array $handles) {
return array();
}
/* -( Integration with Commit Messages )----------------------------------- */
/**
* @task commitmessage
*/
public function shouldAppearInCommitMessage() {
if ($this->getProxy()) {
return $this->getProxy()->shouldAppearInCommitMessage();
}
return false;
}
/**
* @task commitmessage
*/
public function shouldAppearInCommitMessageTemplate() {
if ($this->getProxy()) {
return $this->getProxy()->shouldAppearInCommitMessageTemplate();
}
return false;
}
/**
* @task commitmessage
*/
public function shouldAllowEditInCommitMessage() {
if ($this->getProxy()) {
return $this->getProxy()->shouldAllowEditInCommitMessage();
}
return true;
}
/**
* @task commitmessage
*/
public function getProTips() {
if ($this->getProxy()) {
return $this->getProxy()->getProTips();
}
return array();
}
/**
* @task commitmessage
*/
public function getCommitMessageLabels() {
if ($this->getProxy()) {
return $this->getProxy()->getCommitMessageLabels();
}
return array($this->renderCommitMessageLabel());
}
/**
* @task commitmessage
*/
public function parseValueFromCommitMessage($value) {
if ($this->getProxy()) {
return $this->getProxy()->parseValueFromCommitMessage($value);
}
return $value;
}
/**
* @task commitmessage
*/
public function readValueFromCommitMessage($value) {
if ($this->getProxy()) {
$this->getProxy()->readValueFromCommitMessage($value);
return $this;
}
return $this;
}
/**
* @task commitmessage
*/
public function shouldOverwriteWhenCommitMessageIsEdited() {
if ($this->getProxy()) {
return $this->getProxy()->shouldOverwriteWhenCommitMessageIsEdited();
}
return false;
}
/**
* @task commitmessage
*/
public function getRequiredHandlePHIDsForCommitMessage() {
if ($this->getProxy()) {
return $this->getProxy()->getRequiredHandlePHIDsForCommitMessage();
}
return array();
}
/**
* @task commitmessage
*/
public function renderCommitMessageLabel() {
if ($this->getProxy()) {
return $this->getProxy()->renderCommitMessageLabel();
}
return $this->getFieldName();
}
/**
* @task commitmessage
*/
public function renderCommitMessageValue(array $handles) {
if ($this->getProxy()) {
return $this->getProxy()->renderCommitMessageValue($handles);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
/**
* @task commitmessage
*/
public function validateCommitMessageValue($value) {
if ($this->getProxy()) {
return $this->getProxy()->validateCommitMessageValue($value);
}
return;
}
/* -( Integration with Diff Properties )----------------------------------- */
/**
* @task diff
*/
public function shouldAppearInDiffPropertyView() {
if ($this->getProxy()) {
return $this->getProxy()->shouldAppearInDiffPropertyView();
}
return false;
}
/**
* @task diff
*/
public function renderDiffPropertyViewLabel(DifferentialDiff $diff) {
if ($this->proxy) {
return $this->proxy->renderDiffPropertyViewLabel($diff);
}
return $this->getFieldName();
}
/**
* @task diff
*/
public function renderDiffPropertyViewValue(DifferentialDiff $diff) {
if ($this->proxy) {
return $this->proxy->renderDiffPropertyViewValue($diff);
}
throw new PhabricatorCustomFieldImplementationIncompleteException($this);
}
}
diff --git a/src/applications/differential/customfield/DifferentialReviewersField.php b/src/applications/differential/customfield/DifferentialReviewersField.php
index 9ab77c26ac..749f1e7ace 100644
--- a/src/applications/differential/customfield/DifferentialReviewersField.php
+++ b/src/applications/differential/customfield/DifferentialReviewersField.php
@@ -1,227 +1,228 @@
<?php
final class DifferentialReviewersField
extends DifferentialCoreCustomField {
public function getFieldKey() {
return 'differential:reviewers';
}
public function getFieldKeyForConduit() {
return 'reviewerPHIDs';
}
public function getFieldName() {
return pht('Reviewers');
}
public function getFieldDescription() {
return pht('Manage reviewers.');
}
protected function readValueFromRevision(
DifferentialRevision $revision) {
return $revision->getReviewerStatus();
}
public function getNewValueForApplicationTransactions() {
$specs = array();
foreach ($this->getValue() as $reviewer) {
$specs[$reviewer->getReviewerPHID()] = array(
'data' => $reviewer->getEdgeData(),
);
}
return array('=' => $specs);
}
public function readValueFromRequest(AphrontRequest $request) {
// Compute a new set of reviewer objects. For reviewers who haven't been
// added or removed, retain their existing status. Also, respect the new
// order.
$old_status = $this->getValue();
$old_status = mpull($old_status, null, 'getReviewerPHID');
$new_phids = $request->getArr($this->getFieldKey());
$new_phids = array_fuse($new_phids);
$new_status = array();
foreach ($new_phids as $new_phid) {
if (empty($old_status[$new_phid])) {
$new_status[$new_phid] = new DifferentialReviewer(
$new_phid,
array(
'status' => DifferentialReviewerStatus::STATUS_ADDED,
));
} else {
$new_status[$new_phid] = $old_status[$new_phid];
}
}
$this->setValue($new_status);
}
public function renderEditControl(array $handles) {
$phids = array();
if ($this->getValue()) {
$phids = mpull($this->getValue(), 'getReviewerPHID');
}
return id(new AphrontFormTokenizerControl())
->setUser($this->getViewer())
->setName($this->getFieldKey())
->setDatasource(new PhabricatorProjectOrUserDatasource())
->setValue($phids)
->setError($this->getFieldError())
->setLabel($this->getFieldName());
}
public function getApplicationTransactionType() {
return PhabricatorTransactions::TYPE_EDGE;
}
public function getApplicationTransactionMetadata() {
return array(
'edge:type' => DifferentialRevisionHasReviewerEdgeType::EDGECONST,
);
}
public function shouldAppearInPropertyView() {
return true;
}
public function renderPropertyViewLabel() {
return $this->getFieldName();
}
public function getRequiredHandlePHIDsForPropertyView() {
return mpull($this->getUserReviewers(), 'getReviewerPHID');
}
public function renderPropertyViewValue(array $handles) {
$reviewers = $this->getUserReviewers();
if (!$reviewers) {
return phutil_tag('em', array(), pht('None'));
}
$view = id(new DifferentialReviewersView())
->setUser($this->getViewer())
->setReviewers($reviewers)
->setHandles($handles);
// TODO: Active diff stuff.
return $view;
}
private function getUserReviewers() {
$reviewers = array();
foreach ($this->getObject()->getReviewerStatus() as $reviewer) {
if ($reviewer->isUser()) {
$reviewers[] = $reviewer;
}
}
return $reviewers;
}
public function shouldAppearInCommitMessage() {
return true;
}
public function shouldAppearInCommitMessageTemplate() {
return true;
}
public function getCommitMessageLabels() {
return array(
'Reviewer',
'Reviewers',
);
}
public function parseValueFromCommitMessage($value) {
return $this->parseObjectList(
$value,
array(
PhabricatorPeopleUserPHIDType::TYPECONST,
PhabricatorProjectProjectPHIDType::TYPECONST,
+ PhabricatorOwnersPackagePHIDType::TYPECONST,
));
}
public function getRequiredHandlePHIDsForCommitMessage() {
return mpull($this->getValue(), 'getReviewerPHID');
}
public function readValueFromCommitMessage($value) {
$current_reviewers = $this->getObject()->getReviewerStatus();
$current_reviewers = mpull($current_reviewers, null, 'getReviewerPHID');
$reviewers = array();
foreach ($value as $phid) {
$reviewer = idx($current_reviewers, $phid);
if ($reviewer) {
$reviewers[] = $reviewer;
} else {
$data = array(
'status' => DifferentialReviewerStatus::STATUS_ADDED,
);
$reviewers[] = new DifferentialReviewer($phid, $data);
}
}
$this->setValue($reviewers);
return $this;
}
public function renderCommitMessageValue(array $handles) {
return $this->renderObjectList($handles);
}
public function validateCommitMessageValue($value) {
$author_phid = $this->getObject()->getAuthorPHID();
$config_self_accept_key = 'differential.allow-self-accept';
$allow_self_accept = PhabricatorEnv::getEnvConfig($config_self_accept_key);
foreach ($value as $phid) {
if (($phid == $author_phid) && !$allow_self_accept) {
throw new DifferentialFieldValidationException(
pht('The author of a revision can not be a reviewer.'));
}
}
}
public function getRequiredHandlePHIDsForRevisionHeaderWarnings() {
return mpull($this->getValue(), 'getReviewerPHID');
}
public function getWarningsForRevisionHeader(array $handles) {
$revision = $this->getObject();
$status_needs_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
if ($revision->getStatus() != $status_needs_review) {
return array();
}
foreach ($this->getValue() as $reviewer) {
if (!$handles[$reviewer->getReviewerPHID()]->isDisabled()) {
return array();
}
}
$warnings = array();
if ($this->getValue()) {
$warnings[] = pht(
'This revision needs review, but all specified reviewers are '.
'disabled or inactive.');
} else {
$warnings[] = pht(
'This revision needs review, but there are no reviewers specified.');
}
return $warnings;
}
}
diff --git a/src/applications/differential/customfield/DifferentialSubscribersField.php b/src/applications/differential/customfield/DifferentialSubscribersField.php
index a1e8d1dbd6..b8423e9879 100644
--- a/src/applications/differential/customfield/DifferentialSubscribersField.php
+++ b/src/applications/differential/customfield/DifferentialSubscribersField.php
@@ -1,92 +1,93 @@
<?php
final class DifferentialSubscribersField
extends DifferentialCoreCustomField {
public function getFieldKey() {
return 'differential:subscribers';
}
public function getFieldKeyForConduit() {
return 'ccPHIDs';
}
public function getFieldName() {
return pht('Subscribers');
}
public function getFieldDescription() {
return pht('Manage subscribers.');
}
protected function readValueFromRevision(
DifferentialRevision $revision) {
if (!$revision->getPHID()) {
return array();
}
return PhabricatorSubscribersQuery::loadSubscribersForPHID(
$revision->getPHID());
}
public function getNewValueForApplicationTransactions() {
return array('=' => $this->getValue());
}
public function readValueFromRequest(AphrontRequest $request) {
$this->setValue($request->getArr($this->getFieldKey()));
}
public function renderEditControl(array $handles) {
return id(new AphrontFormTokenizerControl())
->setUser($this->getViewer())
->setName($this->getFieldKey())
->setDatasource(new PhabricatorMetaMTAMailableDatasource())
->setValue($this->getValue())
->setError($this->getFieldError())
->setLabel($this->getFieldName());
}
public function getApplicationTransactionType() {
return PhabricatorTransactions::TYPE_SUBSCRIBERS;
}
public function shouldAppearInCommitMessage() {
return true;
}
public function shouldAllowEditInCommitMessage() {
return true;
}
public function shouldAppearInCommitMessageTemplate() {
return true;
}
public function getCommitMessageLabels() {
return array(
'CC',
'CCs',
'Subscriber',
'Subscribers',
);
}
public function parseValueFromCommitMessage($value) {
return $this->parseObjectList(
$value,
array(
PhabricatorPeopleUserPHIDType::TYPECONST,
PhabricatorProjectProjectPHIDType::TYPECONST,
+ PhabricatorOwnersPackagePHIDType::TYPECONST,
));
}
public function getRequiredHandlePHIDsForCommitMessage() {
return $this->getValue();
}
public function renderCommitMessageValue(array $handles) {
return $this->renderObjectList($handles);
}
}
diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php
index aa5ae1c6f3..52f30e0994 100644
--- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php
+++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php
@@ -1,348 +1,363 @@
<?php
final class PhabricatorOwnersPackageTransactionEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorOwnersApplication';
}
public function getEditorObjectsDescription() {
return pht('Owners Packages');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorOwnersPackageTransaction::TYPE_NAME;
$types[] = PhabricatorOwnersPackageTransaction::TYPE_OWNERS;
$types[] = PhabricatorOwnersPackageTransaction::TYPE_AUDITING;
$types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION;
$types[] = PhabricatorOwnersPackageTransaction::TYPE_PATHS;
$types[] = PhabricatorOwnersPackageTransaction::TYPE_STATUS;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorOwnersPackageTransaction::TYPE_NAME:
return $object->getName();
case PhabricatorOwnersPackageTransaction::TYPE_OWNERS:
$phids = mpull($object->getOwners(), 'getUserPHID');
$phids = array_values($phids);
return $phids;
case PhabricatorOwnersPackageTransaction::TYPE_AUDITING:
return (int)$object->getAuditingEnabled();
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
return $object->getDescription();
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
$paths = $object->getPaths();
return mpull($paths, 'getRef');
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
return $object->getStatus();
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorOwnersPackageTransaction::TYPE_NAME:
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
return $xaction->getNewValue();
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
$new = $xaction->getNewValue();
foreach ($new as $key => $info) {
$new[$key]['excluded'] = (int)idx($info, 'excluded');
}
return $new;
case PhabricatorOwnersPackageTransaction::TYPE_AUDITING:
return (int)$xaction->getNewValue();
case PhabricatorOwnersPackageTransaction::TYPE_OWNERS:
$phids = $xaction->getNewValue();
$phids = array_unique($phids);
$phids = array_values($phids);
return $phids;
}
}
protected function transactionHasEffect(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
list($rem, $add) = $diffs;
return ($rem || $add);
}
return parent::transactionHasEffect($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorOwnersPackageTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
$object->setDescription($xaction->getNewValue());
return;
case PhabricatorOwnersPackageTransaction::TYPE_AUDITING:
$object->setAuditingEnabled($xaction->getNewValue());
return;
case PhabricatorOwnersPackageTransaction::TYPE_OWNERS:
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
return;
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
$object->setStatus($xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorOwnersPackageTransaction::TYPE_NAME:
case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION:
case PhabricatorOwnersPackageTransaction::TYPE_AUDITING:
case PhabricatorOwnersPackageTransaction::TYPE_STATUS:
return;
case PhabricatorOwnersPackageTransaction::TYPE_OWNERS:
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$owners = $object->getOwners();
$owners = mpull($owners, null, 'getUserPHID');
$rem = array_diff($old, $new);
foreach ($rem as $phid) {
if (isset($owners[$phid])) {
$owners[$phid]->delete();
unset($owners[$phid]);
}
}
$add = array_diff($new, $old);
foreach ($add as $phid) {
$owners[$phid] = id(new PhabricatorOwnersOwner())
->setPackageID($object->getID())
->setUserPHID($phid)
->save();
}
// TODO: Attach owners here
return;
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
$old = $xaction->getOldValue();
$new = $xaction->getNewValue();
$paths = $object->getPaths();
$diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new);
list($rem, $add) = $diffs;
$set = PhabricatorOwnersPath::getSetFromTransactionValue($rem);
foreach ($paths as $path) {
$ref = $path->getRef();
if (PhabricatorOwnersPath::isRefInSet($ref, $set)) {
$path->delete();
}
}
foreach ($add as $ref) {
$path = PhabricatorOwnersPath::newFromRef($ref)
->setPackageID($object->getID())
->save();
}
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case PhabricatorOwnersPackageTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Package name is required.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
+
+ foreach ($xactions as $xaction) {
+ $new = $xaction->getNewValue();
+ if (preg_match('([,!])', $new)) {
+ $errors[] = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Invalid'),
+ pht(
+ 'Package names may not contain commas (",") or exclamation '.
+ 'marks ("!"). These characters are ambiguous when package '.
+ 'names are parsed from the command line.'),
+ $xaction);
+ }
+ }
+
break;
case PhabricatorOwnersPackageTransaction::TYPE_PATHS:
if (!$xactions) {
continue;
}
$old = mpull($object->getPaths(), 'getRef');
foreach ($xactions as $xaction) {
$new = $xaction->getNewValue();
// Check that we have a list of paths.
if (!is_array($new)) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht('Path specification must be a list of paths.'),
$xaction);
continue;
}
// Check that each item in the list is formatted properly.
$type_exception = null;
foreach ($new as $key => $value) {
try {
PhutilTypeSpec::checkMap(
$value,
array(
'repositoryPHID' => 'string',
'path' => 'string',
'excluded' => 'optional wild',
));
} catch (PhutilTypeCheckException $ex) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Path specification list contains invalid value '.
'in key "%s": %s.',
$key,
$ex->getMessage()),
$xaction);
$type_exception = $ex;
}
}
if ($type_exception) {
continue;
}
// Check that any new paths reference legitimate repositories which
// the viewer has permission to see.
list($rem, $add) = PhabricatorOwnersPath::getTransactionValueChanges(
$old,
$new);
if ($add) {
$repository_phids = ipull($add, 'repositoryPHID');
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getActor())
->withPHIDs($repository_phids)
->execute();
$repositories = mpull($repositories, null, 'getPHID');
foreach ($add as $ref) {
$repository_phid = $ref['repositoryPHID'];
if (isset($repositories[$repository_phid])) {
continue;
}
$errors[] = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Path specification list references repository PHID "%s", '.
'but that is not a valid, visible repository.',
$repository_phid));
}
}
}
break;
}
return $errors;
}
protected function shouldSendMail(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function getMailSubjectPrefix() {
return PhabricatorEnv::getEnvConfig('metamta.package.subject-prefix');
}
protected function getMailTo(PhabricatorLiskDAO $object) {
return array(
$this->requireActor()->getPHID(),
);
}
protected function getMailCC(PhabricatorLiskDAO $object) {
return mpull($object->getOwners(), 'getUserPHID');
}
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
return id(new OwnersPackageReplyHandler())
->setMailReceiver($object);
}
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
$id = $object->getID();
$name = $object->getName();
return id(new PhabricatorMetaMTAMail())
->setSubject($name)
->addHeader('Thread-Topic', $object->getPHID());
}
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
$body = parent::buildMailBody($object, $xactions);
$detail_uri = PhabricatorEnv::getProductionURI(
'/owners/package/'.$object->getID().'/');
$body->addLinkSection(
pht('PACKAGE DETAIL'),
$detail_uri);
return $body;
}
protected function supportsSearch() {
return true;
}
}
diff --git a/src/applications/owners/phid/PhabricatorOwnersPackagePHIDType.php b/src/applications/owners/phid/PhabricatorOwnersPackagePHIDType.php
index 891a9726c8..772bb84fc0 100644
--- a/src/applications/owners/phid/PhabricatorOwnersPackagePHIDType.php
+++ b/src/applications/owners/phid/PhabricatorOwnersPackagePHIDType.php
@@ -1,82 +1,84 @@
<?php
final class PhabricatorOwnersPackagePHIDType extends PhabricatorPHIDType {
const TYPECONST = 'OPKG';
public function getTypeName() {
return pht('Owners Package');
}
public function getTypeIcon() {
return 'fa-list-alt';
}
public function newObject() {
return new PhabricatorOwnersPackage();
}
public function getPHIDTypeApplicationClass() {
return 'PhabricatorOwnersApplication';
}
protected function buildQueryForObjects(
PhabricatorObjectQuery $query,
array $phids) {
return id(new PhabricatorOwnersPackageQuery())
->withPHIDs($phids);
}
public function loadHandles(
PhabricatorHandleQuery $query,
array $handles,
array $objects) {
foreach ($handles as $phid => $handle) {
$package = $objects[$phid];
$monogram = $package->getMonogram();
$name = $package->getName();
$id = $package->getID();
- $handle->setName($monogram);
- $handle->setFullName("{$monogram}: {$name}");
- $handle->setURI("/owners/package/{$id}/");
+ $handle
+ ->setName($monogram)
+ ->setFullName("{$monogram}: {$name}")
+ ->setCommandLineObjectName("{$monogram} {$name}")
+ ->setURI("/owners/package/{$id}/");
if ($package->isArchived()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
}
}
public function canLoadNamedObject($name) {
return preg_match('/^O\d*[1-9]\d*$/i', $name);
}
public function loadNamedObjects(
PhabricatorObjectQuery $query,
array $names) {
$id_map = array();
foreach ($names as $name) {
$id = (int)substr($name, 1);
$id_map[$id][] = $name;
}
$objects = id(new PhabricatorOwnersPackageQuery())
->setViewer($query->getViewer())
->withIDs(array_keys($id_map))
->execute();
$results = array();
foreach ($objects as $id => $object) {
foreach (idx($id_map, $id, array()) as $name) {
$results[$name] = $object;
}
}
return $results;
}
}
diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php
index 2b626f2e5d..f3c04d2a25 100644
--- a/src/applications/phid/PhabricatorObjectHandle.php
+++ b/src/applications/phid/PhabricatorObjectHandle.php
@@ -1,381 +1,395 @@
<?php
final class PhabricatorObjectHandle
extends Phobject
implements PhabricatorPolicyInterface {
const AVAILABILITY_FULL = 'full';
const AVAILABILITY_NONE = 'none';
const AVAILABILITY_PARTIAL = 'partial';
const AVAILABILITY_DISABLED = 'disabled';
const STATUS_OPEN = 'open';
const STATUS_CLOSED = 'closed';
private $uri;
private $phid;
private $type;
private $name;
private $fullName;
private $title;
private $imageURI;
private $icon;
private $tagColor;
private $timestamp;
private $status = self::STATUS_OPEN;
private $availability = self::AVAILABILITY_FULL;
private $complete;
private $objectName;
private $policyFiltered;
private $subtitle;
private $tokenIcon;
+ private $commandLineObjectName;
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function getIcon() {
if ($this->getPolicyFiltered()) {
return 'fa-lock';
}
if ($this->icon) {
return $this->icon;
}
return $this->getTypeIcon();
}
public function setSubtitle($subtitle) {
$this->subtitle = $subtitle;
return $this;
}
public function getSubtitle() {
return $this->subtitle;
}
public function setTagColor($color) {
static $colors;
if (!$colors) {
$colors = array_fuse(array_keys(PHUITagView::getShadeMap()));
}
if (isset($colors[$color])) {
$this->tagColor = $color;
}
return $this;
}
public function getTagColor() {
if ($this->getPolicyFiltered()) {
return 'disabled';
}
if ($this->tagColor) {
return $this->tagColor;
}
return 'blue';
}
public function getIconColor() {
if ($this->tagColor) {
return $this->tagColor;
}
return null;
}
public function setTokenIcon($icon) {
$this->tokenIcon = $icon;
return $this;
}
public function getTokenIcon() {
if ($this->tokenIcon !== null) {
return $this->tokenIcon;
}
return $this->getIcon();
}
public function getTypeIcon() {
if ($this->getPHIDType()) {
return $this->getPHIDType()->getTypeIcon();
}
return null;
}
public function setPolicyFiltered($policy_filered) {
$this->policyFiltered = $policy_filered;
return $this;
}
public function getPolicyFiltered() {
return $this->policyFiltered;
}
public function setObjectName($object_name) {
$this->objectName = $object_name;
return $this;
}
public function getObjectName() {
if (!$this->objectName) {
return $this->getName();
}
return $this->objectName;
}
public function setURI($uri) {
$this->uri = $uri;
return $this;
}
public function getURI() {
return $this->uri;
}
public function setPHID($phid) {
$this->phid = $phid;
return $this;
}
public function getPHID() {
return $this->phid;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
if ($this->name === null) {
if ($this->getPolicyFiltered()) {
return pht('Restricted %s', $this->getTypeName());
} else {
return pht('Unknown Object (%s)', $this->getTypeName());
}
}
return $this->name;
}
public function setAvailability($availability) {
$this->availability = $availability;
return $this;
}
public function getAvailability() {
return $this->availability;
}
public function isDisabled() {
return ($this->getAvailability() == self::AVAILABILITY_DISABLED);
}
public function setStatus($status) {
$this->status = $status;
return $this;
}
public function getStatus() {
return $this->status;
}
public function setFullName($full_name) {
$this->fullName = $full_name;
return $this;
}
public function getFullName() {
if ($this->fullName !== null) {
return $this->fullName;
}
return $this->getName();
}
+ public function setCommandLineObjectName($command_line_object_name) {
+ $this->commandLineObjectName = $command_line_object_name;
+ return $this;
+ }
+
+ public function getCommandLineObjectName() {
+ if ($this->commandLineObjectName !== null) {
+ return $this->commandLineObjectName;
+ }
+
+ return $this->getObjectName();
+ }
+
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
return $this->type;
}
public function setImageURI($uri) {
$this->imageURI = $uri;
return $this;
}
public function getImageURI() {
return $this->imageURI;
}
public function setTimestamp($timestamp) {
$this->timestamp = $timestamp;
return $this;
}
public function getTimestamp() {
return $this->timestamp;
}
public function getTypeName() {
if ($this->getPHIDType()) {
return $this->getPHIDType()->getTypeName();
}
return $this->getType();
}
/**
* Set whether or not the underlying object is complete. See
* @{method:isComplete} for an explanation of what it means to be complete.
*
* @param bool True if the handle represents a complete object.
* @return this
*/
public function setComplete($complete) {
$this->complete = $complete;
return $this;
}
/**
* Determine if the handle represents an object which was completely loaded
* (i.e., the underlying object exists) vs an object which could not be
* completely loaded (e.g., the type or data for the PHID could not be
* identified or located).
*
* Basically, @{class:PhabricatorHandleQuery} gives you back a handle for
* any PHID you give it, but it gives you a complete handle only for valid
* PHIDs.
*
* @return bool True if the handle represents a complete object.
*/
public function isComplete() {
return $this->complete;
}
public function renderLink($name = null) {
return $this->renderLinkWithAttributes($name, array());
}
public function renderHovercardLink($name = null) {
Javelin::initBehavior('phui-hovercards');
$attributes = array(
'sigil' => 'hovercard',
'meta' => array(
'hoverPHID' => $this->getPHID(),
),
);
return $this->renderLinkWithAttributes($name, $attributes);
}
private function renderLinkWithAttributes($name, array $attributes) {
if ($name === null) {
$name = $this->getLinkName();
}
$classes = array();
$classes[] = 'phui-handle';
$title = $this->title;
if ($this->status != self::STATUS_OPEN) {
$classes[] = 'handle-status-'.$this->status;
}
if ($this->availability != self::AVAILABILITY_FULL) {
$classes[] = 'handle-availability-'.$this->availability;
}
if ($this->getType() == PhabricatorPeopleUserPHIDType::TYPECONST) {
$classes[] = 'phui-link-person';
}
$uri = $this->getURI();
$icon = null;
if ($this->getPolicyFiltered()) {
$icon = id(new PHUIIconView())
->setIcon('fa-lock lightgreytext');
}
$attributes = $attributes + array(
'href' => $uri,
'class' => implode(' ', $classes),
'title' => $title,
);
return javelin_tag(
$uri ? 'a' : 'span',
$attributes,
array($icon, $name));
}
public function renderTag() {
return id(new PHUITagView())
->setType(PHUITagView::TYPE_OBJECT)
->setShade($this->getTagColor())
->setIcon($this->getIcon())
->setHref($this->getURI())
->setName($this->getLinkName());
}
public function getLinkName() {
switch ($this->getType()) {
case PhabricatorPeopleUserPHIDType::TYPECONST:
$name = $this->getName();
break;
default:
$name = $this->getFullName();
break;
}
return $name;
}
protected function getPHIDType() {
$types = PhabricatorPHIDType::getAllTypes();
return idx($types, $this->getType());
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_PUBLIC;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
// NOTE: Handles are always visible, they just don't get populated with
// data if the user can't see the underlying object.
return true;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/phid/query/PhabricatorObjectListQuery.php b/src/applications/phid/query/PhabricatorObjectListQuery.php
index c4bf0e9bba..a1fd3a82bd 100644
--- a/src/applications/phid/query/PhabricatorObjectListQuery.php
+++ b/src/applications/phid/query/PhabricatorObjectListQuery.php
@@ -1,150 +1,190 @@
<?php
final class PhabricatorObjectListQuery extends Phobject {
private $viewer;
private $objectList;
private $allowedTypes = array();
private $allowPartialResults;
public function setAllowPartialResults($allow_partial_results) {
$this->allowPartialResults = $allow_partial_results;
return $this;
}
public function getAllowPartialResults() {
return $this->allowPartialResults;
}
public function setAllowedTypes(array $allowed_types) {
$this->allowedTypes = $allowed_types;
return $this;
}
public function getAllowedTypes() {
return $this->allowedTypes;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setObjectList($object_list) {
$this->objectList = $object_list;
return $this;
}
public function getObjectList() {
return $this->objectList;
}
public function execute() {
$names = $this->getObjectList();
- $names = array_unique(array_filter(preg_split('/[\s,]+/', $names)));
- $objects = $this->loadObjects($names);
+ // First, normalize any internal whitespace so we don't get weird results
+ // if linebreaks hit in weird spots.
+ $names = preg_replace('/\s+/', ' ', $names);
+
+ // Split the list on commas.
+ $names = explode(',', $names);
+
+ // Trim and remove empty tokens.
+ foreach ($names as $key => $name) {
+ $name = trim($name);
+
+ if (!strlen($name)) {
+ unset($names[$key]);
+ continue;
+ }
+
+ $names[$key] = $name;
+ }
+
+ // Remove duplicates.
+ $names = array_unique($names);
+
+ $name_map = array();
+ foreach ($names as $name) {
+ $parts = explode(' ', $name);
+
+ // If this looks like a monogram, ignore anything after the first token.
+ // This allows us to parse "O123 Package Name" as though it was "O123",
+ // which we can look up.
+ if (preg_match('/^[A-Z]\d+\z/', $parts[0])) {
+ $name_map[$parts[0]] = $name;
+ } else {
+ // For anything else, split it on spaces and use each token as a
+ // value. This means "alincoln htaft", separated with a space instead
+ // of with a comma, is two different users.
+ foreach ($parts as $part) {
+ $name_map[$part] = $part;
+ }
+ }
+ }
+
+ $objects = $this->loadObjects(array_keys($name_map));
$types = array();
foreach ($objects as $name => $object) {
$types[phid_get_type($object->getPHID())][] = $name;
}
$invalid = array();
if ($this->getAllowedTypes()) {
$allowed = array_fuse($this->getAllowedTypes());
foreach ($types as $type => $names_of_type) {
if (empty($allowed[$type])) {
$invalid[] = $names_of_type;
}
}
}
$invalid = array_mergev($invalid);
$missing = array();
- foreach ($names as $name) {
- if (empty($objects[$name])) {
+ foreach ($name_map as $key => $name) {
+ if (empty($objects[$key])) {
$missing[] = $name;
}
}
// NOTE: We could couple this less tightly with Differential, but it is
// currently the only thing that uses it, and we'd have to add a lot of
// extra API to loosen this. It's not clear that this will be useful
// elsewhere any time soon, so let's cross that bridge when we come to it.
if (!$this->getAllowPartialResults()) {
if ($invalid && $missing) {
throw new DifferentialFieldParseException(
pht(
'The objects you have listed include objects of the wrong '.
'type (%s) and objects which do not exist (%s).',
implode(', ', $invalid),
implode(', ', $missing)));
} else if ($invalid) {
throw new DifferentialFieldParseException(
pht(
'The objects you have listed include objects of the wrong '.
'type (%s).',
implode(', ', $invalid)));
} else if ($missing) {
throw new DifferentialFieldParseException(
pht(
'The objects you have listed include objects which do not '.
'exist (%s).',
implode(', ', $missing)));
}
}
return array_values(array_unique(mpull($objects, 'getPHID')));
}
private function loadObjects($names) {
// First, try to load visible objects using monograms. This covers most
// object types, but does not cover users or user email addresses.
$query = id(new PhabricatorObjectQuery())
->setViewer($this->getViewer())
->withNames($names);
$query->execute();
$objects = $query->getNamedResults();
$results = array();
foreach ($names as $key => $name) {
if (isset($objects[$name])) {
$results[$name] = $objects[$name];
unset($names[$key]);
}
}
if ($names) {
// We still have some symbols we haven't been able to resolve, so try to
// load users. Try by username first...
$users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withUsernames($names)
->execute();
$user_map = array();
foreach ($users as $user) {
$user_map[phutil_utf8_strtolower($user->getUsername())] = $user;
}
foreach ($names as $key => $name) {
$normal_name = phutil_utf8_strtolower($name);
if (isset($user_map[$normal_name])) {
$results[$name] = $user_map[$normal_name];
unset($names[$key]);
}
}
}
return $results;
}
}
diff --git a/src/applications/phid/query/__tests__/PhabricatorObjectListQueryTestCase.php b/src/applications/phid/query/__tests__/PhabricatorObjectListQueryTestCase.php
index e7347d5dbc..b47bedfa1e 100644
--- a/src/applications/phid/query/__tests__/PhabricatorObjectListQueryTestCase.php
+++ b/src/applications/phid/query/__tests__/PhabricatorObjectListQueryTestCase.php
@@ -1,87 +1,101 @@
<?php
final class PhabricatorObjectListQueryTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testObjectListQuery() {
$user = $this->generateNewTestUser();
$name = $user->getUsername();
$phid = $user->getPHID();
-
$result = $this->parseObjectList("@{$name}");
$this->assertEqual(array($phid), $result);
$result = $this->parseObjectList("{$name}");
$this->assertEqual(array($phid), $result);
$result = $this->parseObjectList("{$name}, {$name}");
$this->assertEqual(array($phid), $result);
$result = $this->parseObjectList("@{$name}, {$name}");
$this->assertEqual(array($phid), $result);
$result = $this->parseObjectList('');
$this->assertEqual(array(), $result);
+
+ $package = PhabricatorOwnersPackage::initializeNewPackage($user)
+ ->setName(pht('Query Test Package'))
+ ->save();
+
+ $package_phid = $package->getPHID();
+ $package_mono = $package->getMonogram();
+
+ $result = $this->parseObjectList("{$package_mono} Any Ignored Text");
+ $this->assertEqual(array($package_phid), $result);
+
+ $result = $this->parseObjectList("{$package_mono} Any Text, {$name}");
+ $this->assertEqual(array($package_phid, $phid), $result);
+
+
// Expect failure when loading a user if objects must be of type "DUCK".
$caught = null;
try {
$result = $this->parseObjectList("{$name}", array('DUCK'));
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
// Expect failure when loading an invalid object.
$caught = null;
try {
$result = $this->parseObjectList('invalid');
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
// Expect failure when loading ANY invalid object, by default.
$caught = null;
try {
$result = $this->parseObjectList("{$name}, invalid");
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertTrue($caught instanceof Exception);
// With partial results, this should load the valid user.
$result = $this->parseObjectList("{$name}, invalid", array(), true);
$this->assertEqual(array($phid), $result);
}
private function parseObjectList(
$list,
array $types = array(),
$allow_partial = false) {
$query = id(new PhabricatorObjectListQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->setObjectList($list);
if ($types) {
$query->setAllowedTypes($types);
}
if ($allow_partial) {
$query->setAllowPartialResults(true);
}
return $query->execute();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Nov 6, 2:02 AM (23 h, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
320924
Default Alt Text
(48 KB)

Event Timeline