Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php
index 3084b2d434..27b30a6278 100644
--- a/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php
+++ b/src/applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php
@@ -1,143 +1,159 @@
<?php
final class PhabricatorTypeaheadFunctionHelpController
extends PhabricatorTypeaheadDatasourceController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$class = $request->getURIData('class');
$sources = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorTypeaheadDatasource')
->execute();
if (!isset($sources[$class])) {
return new Aphront404Response();
}
- $source = $sources[$class];
+ $raw_parameters = $request->getStr('parameters');
+ if ($raw_parameters) {
+ $parameters = phutil_json_decode($raw_parameters);
+ } else {
+ $parameters = array();
+ }
+
+ $source = id(clone $sources[$class])
+ ->setParameters($parameters);
+
+ // This can fail for some types of datasources (like the custom field proxy
+ // datasources) if the "parameters" are wrong. Just fail cleanly instead
+ // of fataling.
+ try {
+ $application_class = $source->getDatasourceApplicationClass();
+ } catch (Exception $ex) {
+ return new Aphront404Response();
+ }
- $application_class = $source->getDatasourceApplicationClass();
if ($application_class) {
$result = id(new PhabricatorApplicationQuery())
->setViewer($this->getViewer())
->withClasses(array($application_class))
->execute();
if (!$result) {
return new Aphront404Response();
}
}
$source->setViewer($viewer);
$title = pht('Typeahead Function Help');
$functions = $source->getAllDatasourceFunctions();
ksort($functions);
$content = array();
$content[] = '= '.pht('Overview');
$content[] = pht(
'Typeahead functions are an advanced feature which allow you to build '.
'more powerful queries. This document explains functions available '.
'for the selected control.'.
"\n\n".
'For general help with search, see the [[ %s | Search User Guide ]] in '.
'the documentation.'.
"\n\n".
'Note that different controls support //different// functions '.
'(depending on what the control is doing), so these specific functions '.
'may not work everywhere. You can always check the help for a control '.
'to review which functions are available for that control.',
PhabricatorEnv::getDoclink('Search User Guide'));
$table = array();
$table_header = array(
pht('Function'),
pht('Token Name'),
pht('Summary'),
);
$table[] = '| '.implode(' | ', $table_header).' |';
$table[] = '|---|---|---|';
foreach ($functions as $function => $spec) {
$spec = $spec + array(
'summary' => null,
'arguments' => null,
);
if (idx($spec, 'arguments')) {
$signature = '**'.$function.'(**//'.$spec['arguments'].'//**)**';
} else {
$signature = '**'.$function.'()**';
}
$name = idx($spec, 'name', '');
$summary = idx($spec, 'summary', '');
$table[] = '| '.$signature.' | '.$name.' | '.$summary.' |';
}
$table = implode("\n", $table);
$content[] = '= '.pht('Function Quick Reference');
$content[] = pht(
'This table briefly describes available functions for this control. '.
'For details on a particular function, see the corresponding section '.
'below.');
$content[] = $table;
$content[] = '= '.pht('Using Typeahead Functions');
$content[] = pht(
"In addition to typing user and project names to build queries, you can ".
"also type the names of special functions which give you more options ".
"and the ability to express more complex queries.\n\n".
"Functions have an internal name (like `%s`) and a human-readable name, ".
"like `Current Viewer`. In general, you can type either one to select ".
"the function. You can also click the {nav icon=search} button on any ".
"typeahead control to browse available functions and find this ".
"documentation.\n\n".
"This documentation uses the internal names to make it clear where ".
"tokens begin and end. Specifically, you will find queries written ".
"out like this in the documentation:\n\n%s\n\n".
"When this query is actually shown in the control, it will look more ".
"like this:\n\n%s",
'viewer()',
'> viewer(), alincoln',
'> {nav Current Viewer} {nav alincoln (Abraham Lincoln)}');
$middot = "\xC2\xB7";
foreach ($functions as $function => $spec) {
$arguments = idx($spec, 'arguments', '');
$name = idx($spec, 'name');
$content[] = '= '.$function.'('.$arguments.') '.$middot.' '.$name;
$content[] = $spec['description'];
}
$content = implode("\n\n", $content);
$content_box = new PHUIRemarkupView($viewer, $content);
$header = id(new PHUIHeaderView())
->setHeader($title);
$document = id(new PHUIDocumentViewPro())
->setHeader($header)
->appendChild($content_box);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Function Help'));
$crumbs->setBorder(true);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($document);
}
}
diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
index 2f55c26384..e0ba9a763b 100644
--- a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
+++ b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
@@ -1,418 +1,424 @@
<?php
final class PhabricatorTypeaheadModularDatasourceController
extends PhabricatorTypeaheadDatasourceController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest();
$viewer = $request->getUser();
$query = $request->getStr('q');
$offset = $request->getInt('offset');
$select_phid = null;
$is_browse = ($request->getURIData('action') == 'browse');
$select = $request->getStr('select');
if ($select) {
$select = phutil_json_decode($select);
$query = idx($select, 'q');
$offset = idx($select, 'offset');
$select_phid = idx($select, 'phid');
}
// Default this to the query string to make debugging a little bit easier.
$raw_query = nonempty($request->getStr('raw'), $query);
// This makes form submission easier in the debug view.
$class = nonempty($request->getURIData('class'), $request->getStr('class'));
$sources = id(new PhutilClassMapQuery())
->setAncestorClass('PhabricatorTypeaheadDatasource')
->execute();
if (isset($sources[$class])) {
$source = $sources[$class];
$source->setParameters($request->getRequestData());
$source->setViewer($viewer);
// NOTE: Wrapping the source in a Composite datasource ensures we perform
// application visibility checks for the viewer, so we do not need to do
// those separately.
$composite = new PhabricatorTypeaheadRuntimeCompositeDatasource();
$composite->addDatasource($source);
$hard_limit = 1000;
$limit = 100;
$composite
->setViewer($viewer)
->setQuery($query)
->setRawQuery($raw_query)
->setLimit($limit + 1);
if ($is_browse) {
if (!$composite->isBrowsable()) {
return new Aphront404Response();
}
if (($offset + $limit) >= $hard_limit) {
// Offset-based paging is intrinsically slow; hard-cap how far we're
// willing to go with it.
return new Aphront404Response();
}
$composite
->setOffset($offset)
->setIsBrowse(true);
}
$results = $composite->loadResults();
if ($is_browse) {
// If this is a request for a specific token after the user clicks
// "Select", return the token in wire format so it can be added to
// the tokenizer.
if ($select_phid !== null) {
$map = mpull($results, null, 'getPHID');
$token = idx($map, $select_phid);
if (!$token) {
return new Aphront404Response();
}
$payload = array(
'key' => $token->getPHID(),
'token' => $token->getWireFormat(),
);
return id(new AphrontAjaxResponse())->setContent($payload);
}
$format = $request->getStr('format');
switch ($format) {
case 'html':
case 'dialog':
// These are the acceptable response formats.
break;
default:
// Return a dialog if format information is missing or invalid.
$format = 'dialog';
break;
}
$next_link = null;
if (count($results) > $limit) {
$results = array_slice($results, 0, $limit, $preserve_keys = true);
if (($offset + (2 * $limit)) < $hard_limit) {
$next_uri = id(new PhutilURI($request->getRequestURI()))
->setQueryParam('offset', $offset + $limit)
->setQueryParam('q', $query)
->setQueryParam('raw', $raw_query)
->setQueryParam('format', 'html');
$next_link = javelin_tag(
'a',
array(
'href' => $next_uri,
'class' => 'typeahead-browse-more',
'sigil' => 'typeahead-browse-more',
'mustcapture' => true,
),
pht('More Results'));
} else {
// If the user has paged through more than 1K results, don't
// offer to page any further.
$next_link = javelin_tag(
'div',
array(
'class' => 'typeahead-browse-hard-limit',
),
pht('You reach the edge of the abyss.'));
}
}
$exclude = $request->getStrList('exclude');
$exclude = array_fuse($exclude);
$select = array(
'offset' => $offset,
'q' => $query,
);
$items = array();
foreach ($results as $result) {
// Disable already-selected tokens.
$disabled = isset($exclude[$result->getPHID()]);
$value = $select + array('phid' => $result->getPHID());
$value = json_encode($value);
$button = phutil_tag(
'button',
array(
'class' => 'small grey',
'name' => 'select',
'value' => $value,
'disabled' => $disabled ? 'disabled' : null,
),
pht('Select'));
$information = $this->renderBrowseResult($result, $button);
$items[] = phutil_tag(
'div',
array(
'class' => 'typeahead-browse-item grouped',
),
$information);
}
$markup = array(
$items,
$next_link,
);
if ($format == 'html') {
$content = array(
'markup' => hsprintf('%s', $markup),
);
return id(new AphrontAjaxResponse())->setContent($content);
}
$this->requireResource('typeahead-browse-css');
$this->initBehavior('typeahead-browse');
$input_id = celerity_generate_unique_node_id();
$frame_id = celerity_generate_unique_node_id();
$config = array(
'inputID' => $input_id,
'frameID' => $frame_id,
'uri' => (string)$request->getRequestURI(),
);
$this->initBehavior('typeahead-search', $config);
$search = javelin_tag(
'input',
array(
'type' => 'text',
'id' => $input_id,
'class' => 'typeahead-browse-input',
'autocomplete' => 'off',
'placeholder' => $source->getPlaceholderText(),
));
$frame = phutil_tag(
'div',
array(
'class' => 'typeahead-browse-frame',
'id' => $frame_id,
),
$markup);
$browser = array(
phutil_tag(
'div',
array(
'class' => 'typeahead-browse-header',
),
$search),
$frame,
);
$function_help = null;
if ($source->getAllDatasourceFunctions()) {
$reference_uri = '/typeahead/help/'.get_class($source).'/';
+ $parameters = $source->getParameters();
+ if ($parameters) {
+ $reference_uri = (string)id(new PhutilURI($reference_uri))
+ ->setQueryParam('parameters', phutil_json_encode($parameters));
+ }
+
$reference_link = phutil_tag(
'a',
array(
'href' => $reference_uri,
'target' => '_blank',
),
pht('Reference: Advanced Functions'));
$function_help = array(
id(new PHUIIconView())
->setIcon('fa-book'),
' ',
$reference_link,
);
}
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FORM)
->setRenderDialogAsDiv(true)
->setTitle($source->getBrowseTitle())
->appendChild($browser)
->setResizeX(true)
->setResizeY($frame_id)
->addFooter($function_help)
->addCancelButton('/', pht('Close'));
}
} else if ($is_browse) {
return new Aphront404Response();
} else {
$results = array();
}
$content = mpull($results, 'getWireFormat');
$content = array_values($content);
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())->setContent($content);
}
// If there's a non-Ajax request to this endpoint, show results in a tabular
// format to make it easier to debug typeahead output.
foreach ($sources as $key => $source) {
// This can happen with composite or generic sources.
if (!$source->getDatasourceApplicationClass()) {
continue;
}
if (!PhabricatorApplication::isClassInstalledForViewer(
$source->getDatasourceApplicationClass(),
$viewer)) {
unset($sources[$key]);
}
}
$options = array_fuse(array_keys($sources));
asort($options);
$form = id(new AphrontFormView())
->setUser($viewer)
->setAction('/typeahead/class/')
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Source Class'))
->setName('class')
->setValue($class)
->setOptions($options))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Query'))
->setName('q')
->setValue($request->getStr('q')))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Raw Query'))
->setName('raw')
->setValue($request->getStr('raw')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Query')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Token Query'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
// Make "\n" delimiters more visible.
foreach ($content as $key => $row) {
$content[$key][0] = str_replace("\n", '<\n>', $row[0]);
}
$table = new AphrontTableView($content);
$table->setHeaders(
array(
pht('Name'),
pht('URI'),
pht('PHID'),
pht('Priority'),
pht('Display Name'),
pht('Display Type'),
pht('Image URI'),
pht('Priority Type'),
pht('Icon'),
pht('Closed'),
pht('Sprite'),
pht('Color'),
pht('Type'),
pht('Unique'),
pht('Auto'),
pht('Phase'),
));
$result_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Token Results (%s)', $class))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($table);
$title = pht('Typeahead Results');
$header = id(new PHUIHeaderView())
->setHeader($title);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$form_box,
$result_box,
));
return $this->newPage()
->setTitle($title)
->appendChild($view);
}
private function renderBrowseResult(
PhabricatorTypeaheadResult $result,
$button) {
$class = array();
$style = array();
$separator = " \xC2\xB7 ";
$class[] = 'phabricator-main-search-typeahead-result';
$name = phutil_tag(
'div',
array(
'class' => 'result-name',
),
$result->getDisplayName());
$icon = $result->getIcon();
$icon = id(new PHUIIconView())->setIcon($icon);
$attributes = $result->getAttributes();
$attributes = phutil_implode_html($separator, $attributes);
$attributes = array($icon, ' ', $attributes);
$closed = $result->getClosed();
if ($closed) {
$class[] = 'result-closed';
$attributes = array($closed, $separator, $attributes);
}
$attributes = phutil_tag(
'div',
array(
'class' => 'result-type',
),
$attributes);
$image = $result->getImageURI();
if ($image) {
$style[] = 'background-image: url('.$image.');';
$class[] = 'has-image';
}
return phutil_tag(
'div',
array(
'class' => implode(' ', $class),
'style' => implode(' ', $style),
),
array(
$button,
$name,
$attributes,
));
}
}
diff --git a/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php
index 9560620db4..31f189988c 100644
--- a/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php
+++ b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php
@@ -1,70 +1,70 @@
<?php
final class PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource
extends PhabricatorTypeaheadDatasource {
public function getBrowseTitle() {
return pht('Browse Any');
}
public function getPlaceholderText() {
return pht('Type "any()"...');
}
public function getDatasourceApplicationClass() {
return null;
}
public function getDatasourceFunctions() {
return array(
'any' => array(
'name' => pht('Any Value'),
'summary' => pht('Find results with any value.'),
'description' => pht(
"This function includes results which have any value. Use a query ".
- "like this to find results with any value:\n\n%s".
+ "like this to find results with any value:\n\n%s",
'> any()'),
),
);
}
public function loadResults() {
$results = array(
$this->newAnyFunction(),
);
return $this->filterResultsAgainstTokens($results);
}
protected function evaluateFunction($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = new PhabricatorQueryConstraint(
PhabricatorQueryConstraint::OPERATOR_ANY,
null);
}
return $results;
}
public function renderFunctionTokens($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
$this->newAnyFunction());
}
return $results;
}
private function newAnyFunction() {
$name = pht('Any Value');
return $this->newFunctionResult()
->setName($name.' any')
->setDisplayName($name)
->setIcon('fa-circle-o')
->setPHID('any()')
->setUnique(true)
->addAttribute(pht('Select results with any value.'));
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 15:58 (2 w, 6 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126245
Default Alt Text
(20 KB)

Event Timeline