diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
index b767ead4f0..b696645e55 100644
--- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
@@ -1,206 +1,213 @@
getViewer();
$method_name = $request->getURIData('method');
$method = id(new PhabricatorConduitMethodQuery())
->setViewer($viewer)
->withMethods(array($method_name))
->executeOne();
if (!$method) {
return new Aphront404Response();
}
$method->setViewer($viewer);
$call_uri = '/api/'.$method->getAPIMethodName();
$errors = array();
$form = id(new AphrontFormView())
->setAction($call_uri)
->setUser($request->getUser())
->appendRemarkupInstructions(
pht(
'Enter parameters using **JSON**. For instance, to enter a '.
'list, type: `%s`',
'["apple", "banana", "cherry"]'));
$params = $method->getParamTypes();
foreach ($params as $param => $desc) {
$form->appendChild(
id(new AphrontFormTextControl())
->setLabel($param)
->setName("params[{$param}]")
->setCaption($desc));
}
$must_login = !$viewer->isLoggedIn() &&
$method->shouldRequireAuthentication();
if ($must_login) {
$errors[] = pht(
'Login Required: This method requires authentication. You must '.
'log in before you can make calls to it.');
} else {
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Output Format'))
->setName('output')
->setOptions(
array(
'human' => pht('Human Readable'),
'json' => pht('JSON'),
)))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($this->getApplicationURI())
->setValue(pht('Call Method')));
}
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($method->getAPIMethodName())
->setHeaderIcon('fa-tty');
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Call Method'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$properties = $this->buildMethodProperties($method);
$info_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('API Method: %s', $method->getAPIMethodName()))
->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($properties);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($method->getAPIMethodName());
$crumbs->setBorder(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$info_box,
$method->getMethodDocumentation(),
$form_box,
$this->renderExampleBox($method, null),
));
$title = $method->getAPIMethodName();
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildMethodProperties(ConduitAPIMethod $method) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView());
$status = $method->getMethodStatus();
$reason = $method->getMethodStatusDescription();
switch ($status) {
case ConduitAPIMethod::METHOD_STATUS_UNSTABLE:
$stability_icon = 'fa-exclamation-triangle yellow';
$stability_label = pht('Unstable Method');
$stability_info = nonempty(
$reason,
pht(
'This method is new and unstable. Its interface is subject '.
'to change.'));
break;
case ConduitAPIMethod::METHOD_STATUS_DEPRECATED:
$stability_icon = 'fa-exclamation-triangle red';
$stability_label = pht('Deprecated Method');
$stability_info = nonempty($reason, pht('This method is deprecated.'));
break;
+ case ConduitAPIMethod::METHOD_STATUS_FROZEN:
+ $stability_icon = 'fa-archive grey';
+ $stability_label = pht('Frozen Method');
+ $stability_info = nonempty(
+ $reason,
+ pht('This method is frozen and will eventually be deprecated.'));
+ break;
default:
$stability_label = null;
break;
}
if ($stability_label) {
$view->addProperty(
pht('Stability'),
array(
id(new PHUIIconView())->setIcon($stability_icon),
' ',
phutil_tag('strong', array(), $stability_label.':'),
' ',
$stability_info,
));
}
$view->addProperty(
pht('Returns'),
$method->getReturnType());
$error_types = $method->getErrorTypes();
$error_types['ERR-CONDUIT-CORE'] = pht('See error message for details.');
$error_description = array();
foreach ($error_types as $error => $meaning) {
$error_description[] = hsprintf(
'
%s: %s',
$error,
$meaning);
}
$error_description = phutil_tag('ul', array(), $error_description);
$view->addProperty(
pht('Errors'),
$error_description);
$scope = $method->getRequiredScope();
switch ($scope) {
case ConduitAPIMethod::SCOPE_ALWAYS:
$oauth_icon = 'fa-globe green';
$oauth_description = pht(
'OAuth clients may always call this method.');
break;
case ConduitAPIMethod::SCOPE_NEVER:
$oauth_icon = 'fa-ban red';
$oauth_description = pht(
'OAuth clients may never call this method.');
break;
default:
$oauth_icon = 'fa-unlock-alt blue';
$oauth_description = pht(
'OAuth clients may call this method after requesting access to '.
'the "%s" scope.',
$scope);
break;
}
$view->addProperty(
pht('OAuth Scope'),
array(
id(new PHUIIconView())->setIcon($oauth_icon),
' ',
$oauth_description,
));
$view->addSectionHeader(
pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent(
new PHUIRemarkupView($viewer, $method->getMethodDescription()));
return $view;
}
}
diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php
index 3a2cc6e30d..05831a782d 100644
--- a/src/applications/conduit/method/ConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitAPIMethod.php
@@ -1,411 +1,412 @@
getMethodDescription();
}
/**
* Get a detailed description of the method.
*
* This method should return remarkup.
*
* @return string Detailed description of the method.
* @task info
*/
abstract public function getMethodDescription();
public function getMethodDocumentation() {
return null;
}
abstract protected function defineParamTypes();
abstract protected function defineReturnType();
protected function defineErrorTypes() {
return array();
}
abstract protected function execute(ConduitAPIRequest $request);
public function isInternalAPI() {
return false;
}
public function getParamTypes() {
$types = $this->defineParamTypes();
$query = $this->newQueryObject();
if ($query) {
$types['order'] = 'optional order';
$types += $this->getPagerParamTypes();
}
return $types;
}
public function getReturnType() {
return $this->defineReturnType();
}
public function getErrorTypes() {
return $this->defineErrorTypes();
}
/**
* This is mostly for compatibility with
* @{class:PhabricatorCursorPagedPolicyAwareQuery}.
*/
public function getID() {
return $this->getAPIMethodName();
}
/**
* Get the status for this method (e.g., stable, unstable or deprecated).
* Should return a METHOD_STATUS_* constant. By default, methods are
* "stable".
*
* @return const METHOD_STATUS_* constant.
* @task status
*/
public function getMethodStatus() {
return self::METHOD_STATUS_STABLE;
}
/**
* Optional description to supplement the method status. In particular, if
* a method is deprecated, you can return a string here describing the reason
* for deprecation and stable alternatives.
*
* @return string|null Description of the method status, if available.
* @task status
*/
public function getMethodStatusDescription() {
return null;
}
public function getErrorDescription($error_code) {
return idx($this->getErrorTypes(), $error_code, pht('Unknown Error'));
}
public function getRequiredScope() {
return self::SCOPE_NEVER;
}
public function executeMethod(ConduitAPIRequest $request) {
$this->setViewer($request->getUser());
return $this->execute($request);
}
abstract public function getAPIMethodName();
/**
* Return a key which sorts methods by application name, then method status,
* then method name.
*/
public function getSortOrder() {
$name = $this->getAPIMethodName();
$map = array(
self::METHOD_STATUS_STABLE => 0,
self::METHOD_STATUS_UNSTABLE => 1,
self::METHOD_STATUS_DEPRECATED => 2,
);
$ord = idx($map, $this->getMethodStatus(), 0);
list($head, $tail) = explode('.', $name, 2);
return "{$head}.{$ord}.{$tail}";
}
public static function getMethodStatusMap() {
$map = array(
self::METHOD_STATUS_STABLE => pht('Stable'),
self::METHOD_STATUS_UNSTABLE => pht('Unstable'),
self::METHOD_STATUS_DEPRECATED => pht('Deprecated'),
);
return $map;
}
public function getApplicationName() {
return head(explode('.', $this->getAPIMethodName(), 2));
}
public static function loadAllConduitMethods() {
return self::newClassMapQuery()->execute();
}
private static function newClassMapQuery() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getAPIMethodName');
}
public static function getConduitMethod($method_name) {
return id(new PhabricatorCachedClassMapQuery())
->setClassMapQuery(self::newClassMapQuery())
->setMapKeyMethod('getAPIMethodName')
->loadClass($method_name);
}
public function shouldRequireAuthentication() {
return true;
}
public function shouldAllowPublic() {
return false;
}
public function shouldAllowUnguardedWrites() {
return false;
}
/**
* Optionally, return a @{class:PhabricatorApplication} which this call is
* part of. The call will be disabled when the application is uninstalled.
*
* @return PhabricatorApplication|null Related application.
*/
public function getApplication() {
return null;
}
protected function formatStringConstants($constants) {
foreach ($constants as $key => $value) {
$constants[$key] = '"'.$value.'"';
}
$constants = implode(', ', $constants);
return 'string-constant<'.$constants.'>';
}
public static function getParameterMetadataKey($key) {
if (strncmp($key, 'api.', 4) === 0) {
// All keys passed beginning with "api." are always metadata keys.
return substr($key, 4);
} else {
switch ($key) {
// These are real keys which always belong to request metadata.
case 'access_token':
case 'scope':
case 'output':
// This is not a real metadata key; it is included here only to
// prevent Conduit methods from defining it.
case '__conduit__':
// This is prevented globally as a blanket defense against OAuth
// redirection attacks. It is included here to stop Conduit methods
// from defining it.
case 'code':
// This is not a real metadata key, but the presence of this
// parameter triggers an alternate request decoding pathway.
case 'params':
return $key;
}
}
return null;
}
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
/* -( Paging Results )----------------------------------------------------- */
/**
* @task pager
*/
protected function getPagerParamTypes() {
return array(
'before' => 'optional string',
'after' => 'optional string',
'limit' => 'optional int (default = 100)',
);
}
/**
* @task pager
*/
protected function newPager(ConduitAPIRequest $request) {
$limit = $request->getValue('limit', 100);
$limit = min(1000, $limit);
$limit = max(1, $limit);
$pager = id(new AphrontCursorPagerView())
->setPageSize($limit);
$before_id = $request->getValue('before');
if ($before_id !== null) {
$pager->setBeforeID($before_id);
}
$after_id = $request->getValue('after');
if ($after_id !== null) {
$pager->setAfterID($after_id);
}
return $pager;
}
/**
* @task pager
*/
protected function addPagerResults(
array $results,
AphrontCursorPagerView $pager) {
$results['cursor'] = array(
'limit' => $pager->getPageSize(),
'after' => $pager->getNextPageID(),
'before' => $pager->getPrevPageID(),
);
return $results;
}
/* -( Implementing Query Methods )----------------------------------------- */
public function newQueryObject() {
return null;
}
protected function newQueryForRequest(ConduitAPIRequest $request) {
$query = $this->newQueryObject();
if (!$query) {
throw new Exception(
pht(
'You can not call newQueryFromRequest() in this method ("%s") '.
'because it does not implement newQueryObject().',
get_class($this)));
}
if (!($query instanceof PhabricatorCursorPagedPolicyAwareQuery)) {
throw new Exception(
pht(
'Call to method newQueryObject() did not return an object of class '.
'"%s".',
'PhabricatorCursorPagedPolicyAwareQuery'));
}
$query->setViewer($request->getUser());
$order = $request->getValue('order');
if ($order !== null) {
if (is_scalar($order)) {
$query->setOrder($order);
} else {
$query->setOrderVector($order);
}
}
return $query;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getPHID() {
return null;
}
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
// Application methods get application visibility; other methods get open
// visibility.
$application = $this->getApplication();
if ($application) {
return $application->getPolicy($capability);
}
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
if (!$this->shouldRequireAuthentication()) {
// Make unauthenticated methods universally visible.
return true;
}
return false;
}
protected function hasApplicationCapability(
$capability,
PhabricatorUser $viewer) {
$application = $this->getApplication();
if (!$application) {
return false;
}
return PhabricatorPolicyFilter::hasCapability(
$viewer,
$application,
$capability);
}
protected function requireApplicationCapability(
$capability,
PhabricatorUser $viewer) {
$application = $this->getApplication();
if (!$application) {
return;
}
PhabricatorPolicyFilter::requireCapability(
$viewer,
$this->getApplication(),
$capability);
}
}
diff --git a/src/applications/conduit/query/PhabricatorConduitMethodQuery.php b/src/applications/conduit/query/PhabricatorConduitMethodQuery.php
index 0409e4ceb3..eb61d8c480 100644
--- a/src/applications/conduit/query/PhabricatorConduitMethodQuery.php
+++ b/src/applications/conduit/query/PhabricatorConduitMethodQuery.php
@@ -1,157 +1,158 @@
methods = $methods;
return $this;
}
public function withNameContains($name_contains) {
$this->nameContains = $name_contains;
return $this;
}
public function withIsStable($is_stable) {
$this->isStable = $is_stable;
return $this;
}
public function withIsUnstable($is_unstable) {
$this->isUnstable = $is_unstable;
return $this;
}
public function withIsDeprecated($is_deprecated) {
$this->isDeprecated = $is_deprecated;
return $this;
}
public function withIsInternal($is_internal) {
$this->isInternal = $is_internal;
return $this;
}
protected function loadPage() {
$methods = $this->getAllMethods();
$methods = $this->filterMethods($methods);
return $methods;
}
private function getAllMethods() {
return id(new PhutilClassMapQuery())
->setAncestorClass('ConduitAPIMethod')
->setSortMethod('getSortOrder')
->execute();
}
private function filterMethods(array $methods) {
foreach ($methods as $key => $method) {
$application = $method->getApplication();
if (!$application) {
continue;
}
if (!$application->isInstalled()) {
unset($methods[$key]);
}
}
$status = array(
- ConduitAPIMethod::METHOD_STATUS_STABLE => $this->isStable,
+ ConduitAPIMethod::METHOD_STATUS_STABLE => $this->isStable,
+ ConduitAPIMethod::METHOD_STATUS_FROZEN => $this->isStable,
ConduitAPIMethod::METHOD_STATUS_DEPRECATED => $this->isDeprecated,
ConduitAPIMethod::METHOD_STATUS_UNSTABLE => $this->isUnstable,
);
// Only apply status filters if any of them are set.
if (array_filter($status)) {
foreach ($methods as $key => $method) {
$keep = idx($status, $method->getMethodStatus());
if (!$keep) {
unset($methods[$key]);
}
}
}
if ($this->nameContains) {
$needle = phutil_utf8_strtolower($this->nameContains);
foreach ($methods as $key => $method) {
$haystack = $method->getAPIMethodName();
$haystack = phutil_utf8_strtolower($haystack);
if (strpos($haystack, $needle) === false) {
unset($methods[$key]);
}
}
}
if ($this->methods) {
$map = array_fuse($this->methods);
foreach ($methods as $key => $method) {
$needle = $method->getAPIMethodName();
if (empty($map[$needle])) {
unset($methods[$key]);
}
}
}
if ($this->isInternal !== null) {
foreach ($methods as $key => $method) {
if ($method->isInternalAPI() !== $this->isInternal) {
unset($methods[$key]);
}
}
}
return $methods;
}
protected function willFilterPage(array $methods) {
$application_phids = array();
foreach ($methods as $method) {
$application = $method->getApplication();
if ($application === null) {
continue;
}
$application_phids[] = $application->getPHID();
}
if ($application_phids) {
$applications = id(new PhabricatorApplicationQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($application_phids)
->execute();
$applications = mpull($applications, null, 'getPHID');
} else {
$applications = array();
}
// Remove methods which belong to an application the viewer can not see.
foreach ($methods as $key => $method) {
$application = $method->getApplication();
if ($application === null) {
continue;
}
if (empty($applications[$application->getPHID()])) {
$this->didRejectResult($method);
unset($methods[$key]);
}
}
return $methods;
}
public function getQueryApplicationClass() {
return 'PhabricatorConduitApplication';
}
}
diff --git a/src/applications/conduit/query/PhabricatorConduitSearchEngine.php b/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
index 3c01005722..787c2154d5 100644
--- a/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
+++ b/src/applications/conduit/query/PhabricatorConduitSearchEngine.php
@@ -1,184 +1,188 @@
setParameter('isStable', $request->getStr('isStable'));
$saved->setParameter('isUnstable', $request->getStr('isUnstable'));
$saved->setParameter('isDeprecated', $request->getStr('isDeprecated'));
$saved->setParameter('nameContains', $request->getStr('nameContains'));
return $saved;
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new PhabricatorConduitMethodQuery());
$query->withIsStable($saved->getParameter('isStable'));
$query->withIsUnstable($saved->getParameter('isUnstable'));
$query->withIsDeprecated($saved->getParameter('isDeprecated'));
$query->withIsInternal(false);
$contains = $saved->getParameter('nameContains');
if (strlen($contains)) {
$query->withNameContains($contains);
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name Contains'))
->setName('nameContains')
->setValue($saved->getParameter('nameContains')));
$is_stable = $saved->getParameter('isStable');
$is_unstable = $saved->getParameter('isUnstable');
$is_deprecated = $saved->getParameter('isDeprecated');
$form
->appendChild(
id(new AphrontFormCheckboxControl())
->setLabel('Stability')
->addCheckbox(
'isStable',
1,
hsprintf(
'%s: %s',
pht('Stable Methods'),
pht('Show established API methods with stable interfaces.')),
$is_stable)
->addCheckbox(
'isUnstable',
1,
hsprintf(
'%s: %s',
pht('Unstable Methods'),
pht('Show new methods which are subject to change.')),
$is_unstable)
->addCheckbox(
'isDeprecated',
1,
hsprintf(
'%s: %s',
pht('Deprecated Methods'),
pht(
'Show old methods which will be deleted in a future '.
'version of Phabricator.')),
$is_deprecated));
}
protected function getURI($path) {
return '/conduit/'.$path;
}
protected function getBuiltinQueryNames() {
return array(
'modern' => pht('Modern Methods'),
'all' => pht('All Methods'),
);
}
public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery();
$query->setQueryKey($query_key);
switch ($query_key) {
case 'modern':
return $query
->setParameter('isStable', true)
->setParameter('isUnstable', true);
case 'all':
return $query
->setParameter('isStable', true)
->setParameter('isUnstable', true)
->setParameter('isDeprecated', true);
}
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function renderResultList(
array $methods,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($methods, 'ConduitAPIMethod');
$viewer = $this->requireViewer();
$out = array();
$last = null;
$list = null;
foreach ($methods as $method) {
$app = $method->getApplicationName();
if ($app !== $last) {
$last = $app;
if ($list) {
$out[] = $list;
}
$list = id(new PHUIObjectItemListView());
$list->setHeader($app);
$app_object = $method->getApplication();
if ($app_object) {
$app_name = $app_object->getName();
} else {
$app_name = $app;
}
}
$method_name = $method->getAPIMethodName();
$item = id(new PHUIObjectItemView())
->setHeader($method_name)
->setHref($this->getApplicationURI('method/'.$method_name.'/'))
->addAttribute($method->getMethodSummary());
switch ($method->getMethodStatus()) {
case ConduitAPIMethod::METHOD_STATUS_STABLE:
break;
case ConduitAPIMethod::METHOD_STATUS_UNSTABLE:
$item->addIcon('fa-warning', pht('Unstable'));
$item->setStatusIcon('fa-warning yellow');
break;
case ConduitAPIMethod::METHOD_STATUS_DEPRECATED:
$item->addIcon('fa-warning', pht('Deprecated'));
$item->setStatusIcon('fa-warning red');
break;
+ case ConduitAPIMethod::METHOD_STATUS_FROZEN:
+ $item->addIcon('fa-archive', pht('Frozen'));
+ $item->setStatusIcon('fa-archive grey');
+ break;
}
$list->addItem($item);
}
if ($list) {
$out[] = $list;
}
$result = new PhabricatorApplicationSearchResultView();
$result->setContent($out);
return $result;
}
}
diff --git a/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php
index e613647935..d71876961e 100644
--- a/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php
+++ b/src/applications/differential/conduit/DifferentialCloseConduitAPIMethod.php
@@ -1,61 +1,71 @@
'required int',
);
}
protected function defineReturnType() {
return 'void';
}
protected function defineErrorTypes() {
return array(
'ERR_NOT_FOUND' => pht('Revision was not found.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$viewer = $request->getUser();
$id = $request->getValue('revisionID');
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($id))
->setViewer($viewer)
->needReviewerStatus(true)
->executeOne();
if (!$revision) {
throw new ConduitException('ERR_NOT_FOUND');
}
$xactions = array();
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(DifferentialTransaction::TYPE_ACTION)
->setNewValue(DifferentialAction::ACTION_CLOSE);
$content_source = $request->newContentSource();
$editor = id(new DifferentialTransactionEditor())
->setActor($viewer)
->setContentSource($request->newContentSource())
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true);
$editor->applyTransactions($revision, $xactions);
return;
}
}
diff --git a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php
index 8f4b154876..459298c54f 100644
--- a/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php
+++ b/src/applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php
@@ -1,92 +1,102 @@
'required revisionid',
'message' => 'optional string',
'action' => 'optional string',
'silent' => 'optional bool',
'attach_inlines' => 'optional bool',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function defineErrorTypes() {
return array(
'ERR_BAD_REVISION' => pht('Bad revision ID.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$viewer = $request->getUser();
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($request->getValue('revision_id')))
->needReviewerStatus(true)
->needReviewerAuthority(true)
->executeOne();
if (!$revision) {
throw new ConduitException('ERR_BAD_REVISION');
}
$xactions = array();
$action = $request->getValue('action');
if ($action && ($action != 'comment') && ($action != 'none')) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(DifferentialTransaction::TYPE_ACTION)
->setNewValue($action);
}
$content = $request->getValue('message');
if (strlen($content)) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
->attachComment(
id(new DifferentialTransactionComment())
->setContent($content));
}
if ($request->getValue('attach_inlines')) {
$type_inline = DifferentialTransaction::TYPE_INLINE;
$inlines = DifferentialTransactionQuery::loadUnsubmittedInlineComments(
$viewer,
$revision);
foreach ($inlines as $inline) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_inline)
->attachComment($inline);
}
}
$editor = id(new DifferentialTransactionEditor())
->setActor($viewer)
->setDisableEmail($request->getValue('silent'))
->setContentSource($request->newContentSource())
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($revision, $xactions);
return array(
'revisionid' => $revision->getID(),
'uri' => PhabricatorEnv::getURI('/D'.$revision->getID()),
);
}
}
diff --git a/src/applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php
index 371d57cd9c..532d63680b 100644
--- a/src/applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php
+++ b/src/applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php
@@ -1,63 +1,73 @@
'ignored',
'diffid' => 'required diffid',
'fields' => 'required dict',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function defineErrorTypes() {
return array(
'ERR_BAD_DIFF' => pht('Bad diff ID.'),
);
}
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 = DifferentialRevision::initializeNewRevision($viewer);
$revision->attachReviewerStatus(array());
$result = $this->applyFieldEdit(
$request,
$revision,
$diff,
$request->getValue('fields', array()),
$message = null);
$revision_id = $result['object']['id'];
return array(
'revisionid' => $revision_id,
'uri' => PhabricatorEnv::getURI('/D'.$revision_id),
);
}
}
diff --git a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php
index 1d4bcc2a8d..720361367a 100644
--- a/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php
+++ b/src/applications/differential/conduit/DifferentialQueryConduitAPIMethod.php
@@ -1,236 +1,246 @@
formatStringConstants($hash_types);
$status_types = array(
DifferentialRevisionQuery::STATUS_ANY,
DifferentialRevisionQuery::STATUS_OPEN,
DifferentialRevisionQuery::STATUS_ACCEPTED,
DifferentialRevisionQuery::STATUS_CLOSED,
);
$status_const = $this->formatStringConstants($status_types);
$order_types = array(
DifferentialRevisionQuery::ORDER_MODIFIED,
DifferentialRevisionQuery::ORDER_CREATED,
);
$order_const = $this->formatStringConstants($order_types);
return array(
'authors' => 'optional list',
'ccs' => 'optional list',
'reviewers' => 'optional list',
'paths' => 'optional list>',
'commitHashes' => 'optional list>',
'status' => 'optional '.$status_const,
'order' => 'optional '.$order_const,
'limit' => 'optional uint',
'offset' => 'optional uint',
'ids' => 'optional list',
'phids' => 'optional list',
'subscribers' => 'optional list',
'responsibleUsers' => 'optional list',
'branches' => 'optional list',
);
}
protected function defineReturnType() {
return 'list';
}
protected function defineErrorTypes() {
return array(
'ERR-INVALID-PARAMETER' => pht('Missing or malformed parameter.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$authors = $request->getValue('authors');
$ccs = $request->getValue('ccs');
$reviewers = $request->getValue('reviewers');
$status = $request->getValue('status');
$order = $request->getValue('order');
$path_pairs = $request->getValue('paths');
$commit_hashes = $request->getValue('commitHashes');
$limit = $request->getValue('limit');
$offset = $request->getValue('offset');
$ids = $request->getValue('ids');
$phids = $request->getValue('phids');
$subscribers = $request->getValue('subscribers');
$responsible_users = $request->getValue('responsibleUsers');
$branches = $request->getValue('branches');
$query = id(new DifferentialRevisionQuery())
->setViewer($request->getUser());
if ($authors) {
$query->withAuthors($authors);
}
if ($ccs) {
$query->withCCs($ccs);
}
if ($reviewers) {
$query->withReviewers($reviewers);
}
if ($path_pairs) {
$paths = array();
foreach ($path_pairs as $pair) {
list($callsign, $path) = $pair;
$paths[] = $path;
}
$path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
if (count($path_map) != count($paths)) {
$unknown_paths = array();
foreach ($paths as $p) {
if (!idx($path_map, $p)) {
$unknown_paths[] = $p;
}
}
throw id(new ConduitException('ERR-INVALID-PARAMETER'))
->setErrorDescription(
pht(
'Unknown paths: %s',
implode(', ', $unknown_paths)));
}
$repos = array();
foreach ($path_pairs as $pair) {
list($callsign, $path) = $pair;
if (!idx($repos, $callsign)) {
$repos[$callsign] = id(new PhabricatorRepositoryQuery())
->setViewer($request->getUser())
->withCallsigns(array($callsign))
->executeOne();
if (!$repos[$callsign]) {
throw id(new ConduitException('ERR-INVALID-PARAMETER'))
->setErrorDescription(
pht(
'Unknown repo callsign: %s',
$callsign));
}
}
$repo = $repos[$callsign];
$query->withPath($repo->getID(), idx($path_map, $path));
}
}
if ($commit_hashes) {
$hash_types = ArcanistDifferentialRevisionHash::getTypes();
foreach ($commit_hashes as $info) {
list($type, $hash) = $info;
if (empty($type) ||
!in_array($type, $hash_types) ||
empty($hash)) {
throw new ConduitException('ERR-INVALID-PARAMETER');
}
}
$query->withCommitHashes($commit_hashes);
}
if ($status) {
$query->withStatus($status);
}
if ($order) {
$query->setOrder($order);
}
if ($limit) {
$query->setLimit($limit);
}
if ($offset) {
$query->setOffset($offset);
}
if ($ids) {
$query->withIDs($ids);
}
if ($phids) {
$query->withPHIDs($phids);
}
if ($responsible_users) {
$query->withResponsibleUsers($responsible_users);
}
if ($subscribers) {
$query->withCCs($subscribers);
}
if ($branches) {
$query->withBranches($branches);
}
$query->needRelationships(true);
$query->needCommitPHIDs(true);
$query->needDiffIDs(true);
$query->needActiveDiffs(true);
$query->needHashes(true);
$revisions = $query->execute();
$field_data = $this->loadCustomFieldsForRevisions(
$request->getUser(),
$revisions);
$results = array();
foreach ($revisions as $revision) {
$diff = $revision->getActiveDiff();
if (!$diff) {
continue;
}
$id = $revision->getID();
$phid = $revision->getPHID();
$result = array(
'id' => $id,
'phid' => $phid,
'title' => $revision->getTitle(),
'uri' => PhabricatorEnv::getProductionURI('/D'.$id),
'dateCreated' => $revision->getDateCreated(),
'dateModified' => $revision->getDateModified(),
'authorPHID' => $revision->getAuthorPHID(),
'status' => $revision->getStatus(),
'statusName' =>
ArcanistDifferentialRevisionStatus::getNameForRevisionStatus(
$revision->getStatus()),
'properties' => $revision->getProperties(),
'branch' => $diff->getBranch(),
'summary' => $revision->getSummary(),
'testPlan' => $revision->getTestPlan(),
'lineCount' => $revision->getLineCount(),
'activeDiffPHID' => $diff->getPHID(),
'diffs' => $revision->getDiffIDs(),
'commits' => $revision->getCommitPHIDs(),
'reviewers' => array_values($revision->getReviewers()),
'ccs' => array_values($revision->getCCPHIDs()),
'hashes' => $revision->getHashes(),
'auxiliary' => idx($field_data, $phid, array()),
'repositoryPHID' => $diff->getRepositoryPHID(),
);
// TODO: This is a hacky way to put permissions on this field until we
// have first-class support, see T838.
if ($revision->getAuthorPHID() == $request->getUser()->getPHID()) {
$result['sourcePath'] = $diff->getSourcePath();
}
$results[] = $result;
}
return $results;
}
}
diff --git a/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php b/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php
index 232dbf79b4..d45dc9749e 100644
--- a/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php
+++ b/src/applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php
@@ -1,79 +1,89 @@
'required revisionid',
'diffid' => 'required diffid',
'fields' => 'required dict',
'message' => 'required string',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function defineErrorTypes() {
return array(
'ERR_BAD_DIFF' => pht('Bad diff ID.'),
'ERR_BAD_REVISION' => pht('Bad revision ID.'),
'ERR_WRONG_USER' => pht('You are not the author of this revision.'),
'ERR_CLOSED' => pht('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');
}
$this->applyFieldEdit(
$request,
$revision,
$diff,
$request->getValue('fields', array()),
$request->getValue('message'));
return array(
'revisionid' => $revision->getID(),
'uri' => PhabricatorEnv::getURI('/D'.$revision->getID()),
);
}
}
diff --git a/src/applications/maniphest/conduit/ManiphestCreateTaskConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestCreateTaskConduitAPIMethod.php
index 3a404774cf..fac96372fe 100644
--- a/src/applications/maniphest/conduit/ManiphestCreateTaskConduitAPIMethod.php
+++ b/src/applications/maniphest/conduit/ManiphestCreateTaskConduitAPIMethod.php
@@ -1,36 +1,46 @@
getTaskFields($is_new = true);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function defineErrorTypes() {
return array(
'ERR-INVALID-PARAMETER' => pht('Missing or malformed parameter.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$task = ManiphestTask::initializeNewTask($request->getUser());
$task = $this->applyRequest($task, $request, $is_new = true);
return $this->buildTaskInfoDictionary($task);
}
}
diff --git a/src/applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php
index 367feff41a..8bd439253f 100644
--- a/src/applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php
+++ b/src/applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php
@@ -1,45 +1,55 @@
'required id',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function defineErrorTypes() {
return array(
'ERR_BAD_TASK' => pht('No such Maniphest task exists.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$task_id = $request->getValue('task_id');
$task = id(new ManiphestTaskQuery())
->setViewer($request->getUser())
->withIDs(array($task_id))
->needSubscriberPHIDs(true)
->needProjectPHIDs(true)
->executeOne();
if (!$task) {
throw new ConduitException('ERR_BAD_TASK');
}
return $this->buildTaskInfoDictionary($task);
}
}
diff --git a/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php
index ea8aeb95ed..45373d2587 100644
--- a/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php
+++ b/src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php
@@ -1,122 +1,132 @@
formatStringConstants($statuses);
$orders = array(
ManiphestTaskQuery::ORDER_PRIORITY,
ManiphestTaskQuery::ORDER_CREATED,
ManiphestTaskQuery::ORDER_MODIFIED,
);
$order_const = $this->formatStringConstants($orders);
return array(
'ids' => 'optional list',
'phids' => 'optional list',
'ownerPHIDs' => 'optional list',
'authorPHIDs' => 'optional list',
'projectPHIDs' => 'optional list',
'ccPHIDs' => 'optional list',
'fullText' => 'optional string',
'status' => 'optional '.$status_const,
'order' => 'optional '.$order_const,
'limit' => 'optional int',
'offset' => 'optional int',
);
}
protected function defineReturnType() {
return 'list';
}
protected function execute(ConduitAPIRequest $request) {
$query = id(new ManiphestTaskQuery())
->setViewer($request->getUser())
->needProjectPHIDs(true)
->needSubscriberPHIDs(true);
$task_ids = $request->getValue('ids');
if ($task_ids) {
$query->withIDs($task_ids);
}
$task_phids = $request->getValue('phids');
if ($task_phids) {
$query->withPHIDs($task_phids);
}
$owners = $request->getValue('ownerPHIDs');
if ($owners) {
$query->withOwners($owners);
}
$authors = $request->getValue('authorPHIDs');
if ($authors) {
$query->withAuthors($authors);
}
$projects = $request->getValue('projectPHIDs');
if ($projects) {
$query->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_AND,
$projects);
}
$ccs = $request->getValue('ccPHIDs');
if ($ccs) {
$query->withSubscribers($ccs);
}
$full_text = $request->getValue('fullText');
if ($full_text) {
$query->withFullTextSearch($full_text);
}
$status = $request->getValue('status');
if ($status) {
$query->withStatus($status);
}
$order = $request->getValue('order');
if ($order) {
$query->setOrder($order);
}
$limit = $request->getValue('limit');
if ($limit) {
$query->setLimit($limit);
}
$offset = $request->getValue('offset');
if ($offset) {
$query->setOffset($offset);
}
$results = $query->execute();
return $this->buildTaskInfoDictionaries($results);
}
}
diff --git a/src/applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php b/src/applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php
index d4adee9570..d8d02becd7 100644
--- a/src/applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php
+++ b/src/applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php
@@ -1,69 +1,79 @@
pht('No such Maniphest task exists.'),
'ERR-INVALID-PARAMETER' => pht('Missing or malformed parameter.'),
'ERR-NO-EFFECT' => pht('Update has no effect.'),
);
}
protected function defineParamTypes() {
return $this->getTaskFields($is_new = false);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function execute(ConduitAPIRequest $request) {
$id = $request->getValue('id');
$phid = $request->getValue('phid');
if (($id && $phid) || (!$id && !$phid)) {
throw new Exception(
pht(
"Specify exactly one of '%s' and '%s'.",
'id',
'phid'));
}
$query = id(new ManiphestTaskQuery())
->setViewer($request->getUser())
->needSubscriberPHIDs(true)
->needProjectPHIDs(true);
if ($id) {
$query->withIDs(array($id));
} else {
$query->withPHIDs(array($phid));
}
$task = $query->executeOne();
$params = $request->getAllParameters();
unset($params['id']);
unset($params['phid']);
if (call_user_func_array('coalesce', $params) === null) {
throw new ConduitException('ERR-NO-EFFECT');
}
if (!$task) {
throw new ConduitException('ERR-BAD-TASK');
}
$task = $this->applyRequest($task, $request, $is_new = false);
return $this->buildTaskInfoDictionary($task);
}
}
diff --git a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php
index b6ccd151ba..3badd04da7 100644
--- a/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php
+++ b/src/applications/paste/conduit/PasteCreateConduitAPIMethod.php
@@ -1,72 +1,82 @@
'required string',
'title' => 'optional string',
'language' => 'optional string',
);
}
protected function defineReturnType() {
return 'nonempty dict';
}
protected function defineErrorTypes() {
return array(
'ERR-NO-PASTE' => pht('Paste may not be empty.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$content = $request->getValue('content');
$title = $request->getValue('title');
$language = $request->getValue('language');
if (!strlen($content)) {
throw new ConduitException('ERR-NO-PASTE');
}
$title = nonempty($title, pht('Masterwork From Distant Lands'));
$language = nonempty($language, '');
$viewer = $request->getUser();
$paste = PhabricatorPaste::initializeNewPaste($viewer);
$xactions = array();
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
->setNewValue($content);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
->setNewValue($language);
$editor = id(new PhabricatorPasteEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContentSource($request->newContentSource());
$xactions = $editor->applyTransactions($paste, $xactions);
$paste->attachRawContent($content);
return $this->buildPasteInfoDictionary($paste);
}
}
diff --git a/src/applications/paste/conduit/PasteQueryConduitAPIMethod.php b/src/applications/paste/conduit/PasteQueryConduitAPIMethod.php
index 6f3f87acf2..f20c18c06a 100644
--- a/src/applications/paste/conduit/PasteQueryConduitAPIMethod.php
+++ b/src/applications/paste/conduit/PasteQueryConduitAPIMethod.php
@@ -1,63 +1,73 @@
'optional list',
'phids' => 'optional list',
'authorPHIDs' => 'optional list',
'after' => 'optional int',
'limit' => 'optional int, default = 100',
);
}
protected function defineReturnType() {
return 'list';
}
protected function execute(ConduitAPIRequest $request) {
$query = id(new PhabricatorPasteQuery())
->setViewer($request->getUser())
->needRawContent(true);
if ($request->getValue('ids')) {
$query->withIDs($request->getValue('ids'));
}
if ($request->getValue('phids')) {
$query->withPHIDs($request->getValue('phids'));
}
if ($request->getValue('authorPHIDs')) {
$query->withAuthorPHIDs($request->getValue('authorPHIDs'));
}
if ($request->getValue('after')) {
$query->setAfterID($request->getValue('after'));
}
$limit = $request->getValue('limit', 100);
if ($limit) {
$query->setLimit($limit);
}
$pastes = $query->execute();
$results = array();
foreach ($pastes as $paste) {
$results[$paste->getPHID()] = $this->buildPasteInfoDictionary($paste);
}
return $results;
}
}
diff --git a/src/applications/people/conduit/UserQueryConduitAPIMethod.php b/src/applications/people/conduit/UserQueryConduitAPIMethod.php
index c42414a6be..df0ec65841 100644
--- a/src/applications/people/conduit/UserQueryConduitAPIMethod.php
+++ b/src/applications/people/conduit/UserQueryConduitAPIMethod.php
@@ -1,82 +1,92 @@
'optional list',
'emails' => 'optional list',
'realnames' => 'optional list',
'phids' => 'optional list',
'ids' => 'optional list',
'offset' => 'optional int',
'limit' => 'optional int (default = 100)',
);
}
protected function defineReturnType() {
return 'list';
}
protected function defineErrorTypes() {
return array(
'ERR-INVALID-PARAMETER' => pht('Missing or malformed parameter.'),
);
}
protected function execute(ConduitAPIRequest $request) {
$usernames = $request->getValue('usernames', array());
$emails = $request->getValue('emails', array());
$realnames = $request->getValue('realnames', array());
$phids = $request->getValue('phids', array());
$ids = $request->getValue('ids', array());
$offset = $request->getValue('offset', 0);
$limit = $request->getValue('limit', 100);
$query = id(new PhabricatorPeopleQuery())
->setViewer($request->getUser())
->needProfileImage(true)
->needAvailability(true);
if ($usernames) {
$query->withUsernames($usernames);
}
if ($emails) {
$query->withEmails($emails);
}
if ($realnames) {
$query->withRealnames($realnames);
}
if ($phids) {
$query->withPHIDs($phids);
}
if ($ids) {
$query->withIDs($ids);
}
if ($limit) {
$query->setLimit($limit);
}
if ($offset) {
$query->setOffset($offset);
}
$users = $query->execute();
$results = array();
foreach ($users as $user) {
$results[] = $this->buildUserInformationDictionary(
$user,
$with_email = false,
$with_availability = true);
}
return $results;
}
}
diff --git a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php
index aefade63a2..66075e7248 100644
--- a/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php
+++ b/src/applications/project/conduit/ProjectCreateConduitAPIMethod.php
@@ -1,81 +1,91 @@
'required string',
'members' => 'optional list',
'icon' => 'optional string',
'color' => 'optional string',
'tags' => 'optional list',
);
}
protected function defineReturnType() {
return 'dict';
}
protected function execute(ConduitAPIRequest $request) {
$user = $request->getUser();
$this->requireApplicationCapability(
ProjectCreateProjectsCapability::CAPABILITY,
$user);
$project = PhabricatorProject::initializeNewProject($user);
$type_name = PhabricatorProjectTransaction::TYPE_NAME;
$members = $request->getValue('members');
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType($type_name)
->setNewValue($request->getValue('name'));
if ($request->getValue('icon')) {
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorProjectTransaction::TYPE_ICON)
->setNewValue($request->getValue('icon'));
}
if ($request->getValue('color')) {
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorProjectTransaction::TYPE_COLOR)
->setNewValue($request->getValue('color'));
}
if ($request->getValue('tags')) {
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorProjectTransaction::TYPE_SLUGS)
->setNewValue($request->getValue('tags'));
}
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue(
'edge:type',
PhabricatorProjectProjectHasMemberEdgeType::EDGECONST)
->setNewValue(
array(
'+' => array_fuse($members),
));
$editor = id(new PhabricatorProjectTransactionEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSource($request->newContentSource());
$editor->applyTransactions($project, $xactions);
return $this->buildProjectInfoDictionary($project);
}
}
diff --git a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php
index 3116ecb7f9..0a02088413 100644
--- a/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php
+++ b/src/applications/project/conduit/ProjectQueryConduitAPIMethod.php
@@ -1,127 +1,137 @@
formatStringConstants($statuses);
return array(
'ids' => 'optional list',
'names' => 'optional list',
'phids' => 'optional list',
'slugs' => 'optional list',
'icons' => 'optional list',
'colors' => 'optional list',
'status' => 'optional '.$status_const,
'members' => 'optional list',
'limit' => 'optional int',
'offset' => 'optional int',
);
}
protected function defineReturnType() {
return 'list';
}
protected function execute(ConduitAPIRequest $request) {
$query = new PhabricatorProjectQuery();
$query->setViewer($request->getUser());
$query->needMembers(true);
$query->needSlugs(true);
$ids = $request->getValue('ids');
if ($ids) {
$query->withIDs($ids);
}
$names = $request->getValue('names');
if ($names) {
$query->withNames($names);
}
$status = $request->getValue('status');
if ($status) {
$query->withStatus($status);
}
$phids = $request->getValue('phids');
if ($phids) {
$query->withPHIDs($phids);
}
$slugs = $request->getValue('slugs');
if ($slugs) {
$query->withSlugs($slugs);
}
$request->getValue('icons');
if ($request->getValue('icons')) {
$icons = array();
$query->withIcons($icons);
}
$colors = $request->getValue('colors');
if ($colors) {
$query->withColors($colors);
}
$members = $request->getValue('members');
if ($members) {
$query->withMemberPHIDs($members);
}
$limit = $request->getValue('limit');
if ($limit) {
$query->setLimit($limit);
}
$offset = $request->getValue('offset');
if ($offset) {
$query->setOffset($offset);
}
$pager = $this->newPager($request);
$results = $query->executeWithCursorPager($pager);
$projects = $this->buildProjectInfoDictionaries($results);
// TODO: This is pretty hideous.
$slug_map = array();
if ($slugs) {
foreach ($slugs as $slug) {
$normal = PhabricatorSlug::normalizeProjectSlug($slug);
foreach ($projects as $project) {
if (in_array($normal, $project['slugs'])) {
$slug_map[$slug] = $project['phid'];
}
}
}
}
$result = array(
'data' => $projects,
'slugMap' => $slug_map,
);
return $this->addPagerResults($result, $pager);
}
}
diff --git a/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php b/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php
index a3c1061e6a..61435e2a1b 100644
--- a/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php
+++ b/src/applications/repository/conduit/RepositoryQueryConduitAPIMethod.php
@@ -1,85 +1,87 @@
'optional list',
'phids' => 'optional list',
'callsigns' => 'optional list',
'vcsTypes' => 'optional list',
'remoteURIs' => 'optional list',
'uuids' => 'optional list',
);
}
protected function defineReturnType() {
return 'list';
}
protected function execute(ConduitAPIRequest $request) {
$query = $this->newQueryForRequest($request);
$ids = $request->getValue('ids', array());
if ($ids) {
$query->withIDs($ids);
}
$phids = $request->getValue('phids', array());
if ($phids) {
$query->withPHIDs($phids);
}
$callsigns = $request->getValue('callsigns', array());
if ($callsigns) {
$query->withCallsigns($callsigns);
}
$vcs_types = $request->getValue('vcsTypes', array());
if ($vcs_types) {
$query->withTypes($vcs_types);
}
$remote_uris = $request->getValue('remoteURIs', array());
if ($remote_uris) {
$query->withURIs($remote_uris);
}
$uuids = $request->getValue('uuids', array());
if ($uuids) {
$query->withUUIDs($uuids);
}
$pager = $this->newPager($request);
$repositories = $query->executeWithCursorPager($pager);
$results = array();
foreach ($repositories as $repository) {
$results[] = $repository->toDictionary();
}
return $results;
}
}
diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
index d050e48bff..3d13f2c3d9 100644
--- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
+++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
@@ -1,610 +1,599 @@
getCustomQueryMaps($query);
// Make sure we emit empty maps as objects, not lists.
foreach ($maps as $key => $map) {
if (!$map) {
$maps[$key] = (object)$map;
}
}
if (!$maps) {
$maps = (object)$maps;
}
return $maps;
}
protected function getCustomQueryMaps($query) {
return array();
}
public function getApplication() {
$engine = $this->newSearchEngine();
$class = $engine->getApplicationClassName();
return PhabricatorApplication::getByClass($class);
}
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodStatusDescription() {
- return pht(
- 'ApplicationSearch methods are fairly stable, but were introduced '.
- 'relatively recently and may continue to evolve as more applications '.
- 'adopt them.');
- }
-
final protected function defineParamTypes() {
return array(
'queryKey' => 'optional string',
'constraints' => 'optional map',
'attachments' => 'optional map',
'order' => 'optional order',
) + $this->getPagerParamTypes();
}
final protected function defineReturnType() {
return 'map';
}
final protected function execute(ConduitAPIRequest $request) {
$engine = $this->newSearchEngine()
->setViewer($request->getUser());
return $engine->buildConduitResponse($request, $this);
}
final public function getMethodDescription() {
return pht(
'This is a standard **ApplicationSearch** method which will let you '.
'list, query, or search for objects. For documentation on these '.
'endpoints, see **[[ %s | Conduit API: Using Search Endpoints ]]**.',
PhabricatorEnv::getDoclink('Conduit API: Using Search Endpoints'));
}
final public function getMethodDocumentation() {
$viewer = $this->getViewer();
$engine = $this->newSearchEngine()
->setViewer($viewer);
$query = $engine->newQuery();
$out = array();
$out[] = $this->buildQueriesBox($engine);
$out[] = $this->buildConstraintsBox($engine);
$out[] = $this->buildOrderBox($engine, $query);
$out[] = $this->buildFieldsBox($engine);
$out[] = $this->buildAttachmentsBox($engine);
$out[] = $this->buildPagingBox($engine);
return $out;
}
private function buildQueriesBox(
PhabricatorApplicationSearchEngine $engine) {
$viewer = $this->getViewer();
$info = pht(<<loadAllNamedQueries();
$rows = array();
foreach ($named_queries as $named_query) {
$builtin = $named_query->getIsBuiltin()
? pht('Builtin')
: pht('Custom');
$rows[] = array(
$named_query->getQueryKey(),
$named_query->getQueryName(),
$builtin,
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Query Key'),
pht('Name'),
pht('Builtin'),
))
->setColumnClasses(
array(
'prewrap',
'pri wide',
null,
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Builtin and Saved Queries'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info))
->appendChild($table);
}
private function buildConstraintsBox(
PhabricatorApplicationSearchEngine $engine) {
$info = pht(<<getSearchFieldsForConduit();
// As a convenience, put these fields at the very top, even if the engine
// specifies and alternate display order for the web UI. These fields are
// very important in the API and nearly useless in the web UI.
$fields = array_select_keys(
$fields,
array('ids', 'phids')) + $fields;
$rows = array();
foreach ($fields as $field) {
$key = $field->getConduitKey();
$label = $field->getLabel();
$type_object = $field->getConduitParameterType();
if ($type_object) {
$type = $type_object->getTypeName();
$description = $field->getDescription();
} else {
$type = null;
$description = phutil_tag('em', array(), pht('Not supported.'));
}
$rows[] = array(
$key,
$label,
$type,
$description,
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Key'),
pht('Label'),
pht('Type'),
pht('Description'),
))
->setColumnClasses(
array(
'prewrap',
'pri',
'prewrap',
'wide',
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Custom Query Constraints'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info))
->appendChild($table);
}
private function buildOrderBox(
PhabricatorApplicationSearchEngine $engine,
$query) {
$orders_info = pht(<<getBuiltinOrders();
$rows = array();
foreach ($orders as $key => $order) {
$rows[] = array(
$key,
$order['name'],
implode(', ', $order['vector']),
);
}
$orders_table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Key'),
pht('Description'),
pht('Columns'),
))
->setColumnClasses(
array(
'pri',
'',
'wide',
));
$columns_info = pht(<<getOrderableColumns();
$rows = array();
foreach ($columns as $key => $column) {
$rows[] = array(
$key,
idx($column, 'unique') ? pht('Yes') : pht('No'),
);
}
$columns_table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Key'),
pht('Unique'),
))
->setColumnClasses(
array(
'pri',
'wide',
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Result Ordering'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($orders_info))
->appendChild($orders_table)
->appendChild($this->buildRemarkup($columns_info))
->appendChild($columns_table);
}
private function buildFieldsBox(
PhabricatorApplicationSearchEngine $engine) {
$info = pht(<<getAllConduitFieldSpecifications();
$rows = array();
foreach ($specs as $key => $spec) {
$type = $spec->getType();
$description = $spec->getDescription();
$rows[] = array(
$key,
$type,
$description,
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Key'),
pht('Type'),
pht('Description'),
))
->setColumnClasses(
array(
'pri',
'mono',
'wide',
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Object Fields'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info))
->appendChild($table);
}
private function buildAttachmentsBox(
PhabricatorApplicationSearchEngine $engine) {
$info = pht(<<getConduitSearchAttachments();
$rows = array();
foreach ($attachments as $key => $attachment) {
$rows[] = array(
$key,
$attachment->getAttachmentName(),
$attachment->getAttachmentDescription(),
);
}
$table = id(new AphrontTableView($rows))
->setNoDataString(pht('This call does not support any attachments.'))
->setHeaders(
array(
pht('Key'),
pht('Name'),
pht('Description'),
))
->setColumnClasses(
array(
'prewrap',
'pri',
'wide',
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Attachments'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info))
->appendChild($table);
}
private function buildPagingBox(
PhabricatorApplicationSearchEngine $engine) {
$info = pht(<<setHeaderText(pht('Paging and Limits'))
->setCollapsed(true)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($this->buildRemarkup($info));
}
private function buildRemarkup($remarkup) {
$viewer = $this->getViewer();
$view = new PHUIRemarkupView($viewer, $remarkup);
$view->setRemarkupOptions(
array(
PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS => false,
));
return id(new PHUIBoxView())
->appendChild($view)
->addPadding(PHUI::PADDING_LARGE);
}
}
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
index de693a4049..3a6dd4b70c 100644
--- a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
@@ -1,171 +1,160 @@
newEditEngine();
$class = $engine->getEngineApplicationClass();
return PhabricatorApplication::getByClass($class);
}
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodStatusDescription() {
- return pht(
- 'ApplicationEditor methods are fairly stable, but were introduced '.
- 'relatively recently and may continue to evolve as more applications '.
- 'adopt them.');
- }
-
final protected function defineParamTypes() {
return array(
'transactions' => 'list