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>', 'objectIdentifier' => 'optional id|phid|string', ); } final protected function defineReturnType() { return 'map'; } final protected function execute(ConduitAPIRequest $request) { $engine = $this->newEditEngine() ->setViewer($request->getUser()); return $engine->buildConduitResponse($request); } final public function getMethodDescription() { return pht( 'This is a standard **ApplicationEditor** method which allows you to '. 'create and modify objects by applying transactions. For documentation '. 'on these endpoints, see '. '**[[ %s | Conduit API: Using Edit Endpoints ]]**.', PhabricatorEnv::getDoclink('Conduit API: Using Edit Endpoints')); } final public function getMethodDocumentation() { $viewer = $this->getViewer(); $engine = $this->newEditEngine() ->setViewer($viewer); $types = $engine->getConduitEditTypes(); $out = array(); $out[] = $this->buildEditTypesBoxes($engine, $types); return $out; } private function buildEditTypesBoxes( PhabricatorEditEngine $engine, array $types) { $boxes = array(); $summary_info = pht( 'This endpoint supports these types of transactions. See below for '. 'detailed information about each transaction type.'); $rows = array(); foreach ($types as $type) { $rows[] = array( $type->getEditType(), $type->getConduitDescription(), ); } $summary_table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Description'), )) ->setColumnClasses( array( 'prewrap', 'wide', )); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Transaction Types')) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($summary_info)) ->appendChild($summary_table); foreach ($types as $type) { $section = array(); $section[] = $type->getConduitDescription(); $type_documentation = $type->getConduitDocumentation(); if (strlen($type_documentation)) { $section[] = $type_documentation; } $section = implode("\n\n", $section); $rows = array(); $rows[] = array( 'type', 'const', $type->getEditType(), ); $rows[] = array( 'value', $type->getConduitType(), $type->getConduitTypeDescription(), ); $type_table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Key'), pht('Type'), pht('Description'), )) ->setColumnClasses( array( 'prewrap', 'prewrap', 'wide', )); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Transaction Type: %s', $type->getEditType())) ->setCollapsed(true) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->appendChild($this->buildRemarkup($section)) ->appendChild($type_table); } return $boxes; } 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); } }