Page MenuHomestyx hydra

No OneTemporary

diff --git a/externals/mimemailparser/attachment.class.php b/externals/mimemailparser/attachment.class.php
index d56aba1f8d..2631a4fc44 100644
--- a/externals/mimemailparser/attachment.class.php
+++ b/externals/mimemailparser/attachment.class.php
@@ -1,136 +1,136 @@
<?php
/**
* Model of an Attachment
*/
class MimeMailParser_attachment {
/**
* @var $filename Filename
*/
public $filename;
/**
* @var $content_type Mime Type
*/
public $content_type;
/**
* @var $content File Content
*/
private $content;
/**
* @var $extension Filename extension
*/
private $extension;
/**
* @var $content_disposition Content-Disposition (attachment or inline)
*/
public $content_disposition;
/**
* @var $headers An Array of the attachment headers
*/
public $headers;
private $stream;
public function __construct($filename, $content_type, $stream, $content_disposition = 'attachment', $headers = array()) {
$this->filename = $filename;
$this->content_type = $content_type;
$this->stream = $stream;
$this->content = null;
$this->content_disposition = $content_disposition;
$this->headers = $headers;
}
/**
* retrieve the attachment filename
* @return String
*/
public function getFilename() {
return $this->filename;
}
/**
* Retrieve the Attachment Content-Type
* @return String
*/
public function getContentType() {
return $this->content_type;
}
/**
* Retrieve the Attachment Content-Disposition
* @return String
*/
public function getContentDisposition() {
return $this->content_disposition;
}
/**
* Retrieve the Attachment Headers
* @return String
*/
public function getHeaders() {
return $this->headers;
}
/**
* Retrieve the file extension
* @return String
*/
public function getFileExtension() {
if (!$this->extension) {
$ext = substr(strrchr($this->filename, '.'), 1);
if ($ext == 'gz') {
// special case, tar.gz
// todo: other special cases?
$ext = preg_match("/\.tar\.gz$/i", $ext) ? 'tar.gz' : 'gz';
}
$this->extension = $ext;
}
return $this->extension;
}
/**
* Read the contents a few bytes at a time until completed
* Once read to completion, it always returns false
* @return String
* @param $bytes Int[optional]
*/
public function read($bytes = 2082) {
return feof($this->stream) ? false : fread($this->stream, $bytes);
}
/**
* Retrieve the file content in one go
- * Once you retreive the content you cannot use MimeMailParser_attachment::read()
+ * Once you retrieve the content you cannot use MimeMailParser_attachment::read()
* @return String
*/
public function getContent() {
if ($this->content === null) {
fseek($this->stream, 0);
while(($buf = $this->read()) !== false) {
$this->content .= $buf;
}
}
return $this->content;
}
/**
* Allow the properties
* MimeMailParser_attachment::$name,
* MimeMailParser_attachment::$extension
* to be retrieved as public properties
* @param $name Object
*/
public function __get($name) {
if ($name == 'content') {
return $this->getContent();
} else if ($name == 'extension') {
return $this->getFileExtension();
}
return null;
}
}
?>
diff --git a/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php b/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php
index 6039c78e87..f1e5aff4ef 100644
--- a/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php
+++ b/src/applications/repository/graphcache/PhabricatorRepositoryGraphCache.php
@@ -1,409 +1,409 @@
<?php
/**
* Given a commit and a path, efficiently determine the most recent ancestor
* commit where the path was touched.
*
* In Git and Mercurial, log operations with a path are relatively slow. For
* example:
*
* git log -n1 <commit> -- <path>
*
* ...routinely takes several hundred milliseconds, and equivalent requests
* often take longer in Mercurial.
*
* Unfortunately, this operation is fundamental to rendering a repository for
* the web, and essentially everything else that's slow can be reduced to this
* plus some trivial work afterward. Making this fast is desirable and powerful,
* and allows us to make other things fast by expressing them in terms of this
* query.
*
* Because the query is fundamentally a graph query, it isn't easy to express
* in a reasonable way in MySQL, and we can't do round trips to the server to
* walk the graph without incurring huge performance penalties.
*
* However, the total amount of data in the graph is relatively small. By
* caching it in chunks and keeping it in APC, we can reasonably load and walk
* the graph in PHP quickly.
*
* For more context, see T2683.
*
* Structure of the Cache
* ======================
*
* The cache divides commits into buckets (see @{method:getBucketSize}). To
* walk the graph, we pull a commit's bucket. The bucket is a map from commit
* IDs to a list of parents and changed paths, separated by `null`. For
* example, a bucket might look like this:
*
* array(
* 1 => array(0, null, 17, 18),
* 2 => array(1, null, 4),
* // ...
* )
*
* This means that commit ID 1 has parent commit 0 (a special value meaning
* no parents) and affected path IDs 17 and 18. Commit ID 2 has parent commit 1,
* and affected path 4.
*
* This data structure attempts to balance compactness, ease of construction,
* simplicity of cache semantics, and lookup performance. In the average case,
* it appears to do a reasonable job at this.
*
* @task query Querying the Graph Cache
* @task cache Cache Internals
*/
final class PhabricatorRepositoryGraphCache {
private $rebuiltKeys = array();
/* -( Querying the Graph Cache )------------------------------------------- */
/**
* Search the graph cache for the most modification to a path.
*
* @param int The commit ID to search ancestors of.
* @param int The path ID to search for changes to.
* @param float Maximum number of seconds to spend trying to satisfy this
* query using the graph cache. By default, `0.5` (500ms).
* @return mixed Commit ID, or `null` if no ancestors exist, or `false` if
* the graph cache was unable to determine the answer.
* @task query
*/
public function loadLastModifiedCommitID($commit_id, $path_id, $time = 0.5) {
$commit_id = (int)$commit_id;
$path_id = (int)$path_id;
$bucket_data = null;
$data_key = null;
$seen = array();
$t_start = microtime(true);
$iterations = 0;
while (true) {
$bucket_key = $this->getBucketKey($commit_id);
if (($data_key != $bucket_key) || $bucket_data === null) {
$bucket_data = $this->getBucketData($bucket_key);
$data_key = $bucket_key;
}
if (empty($bucket_data[$commit_id])) {
// Rebuild the cache bucket, since the commit might be a very recent
// one that we'll pick up by rebuilding.
$bucket_data = $this->getBucketData($bucket_key, $bucket_data);
if (empty($bucket_data[$commit_id])) {
// A rebuild didn't help. This can occur legitimately if the commit
// is new and hasn't parsed yet.
return false;
}
// Otherwise, the rebuild gave us the data, so we can keep going.
}
// Sanity check so we can survive and recover from bad data.
if (isset($seen[$commit_id])) {
phlog(pht('Unexpected infinite loop in RepositoryGraphCache!'));
return false;
} else {
$seen[$commit_id] = true;
}
// `$data` is a list: the commit's parent IDs, followed by `null`,
// followed by the modified paths in ascending order. We figure out the
// first parent first, then check if the path was touched. If the path
// was touched, this is the commit we're after. If not, walk backward
// in the tree.
$items = $bucket_data[$commit_id];
$size = count($items);
// Walk past the parent information.
$parent_id = null;
for ($ii = 0;; ++$ii) {
if ($items[$ii] === null) {
break;
}
if ($parent_id === null) {
$parent_id = $items[$ii];
}
}
// Look for a modification to the path.
for (; $ii < $size; ++$ii) {
$item = $items[$ii];
if ($item > $path_id) {
break;
}
if ($item === $path_id) {
return $commit_id;
}
}
if ($parent_id) {
$commit_id = $parent_id;
// Periodically check if we've spent too long looking for a result
// in the cache, and return so we can fall back to a VCS operation. This
// keeps us from having a degenerate worst case if, e.g., the cache
// is cold and we need to inspect a very large number of blocks
// to satisfy the query.
if (((++$iterations) % 64) === 0) {
$t_end = microtime(true);
if (($t_end - $t_start) > $time) {
return false;
}
}
continue;
}
// If we have an explicit 0, that means this commit really has no parents.
// Usually, it is the first commit in the repository.
if ($parent_id === 0) {
return null;
}
// If we didn't find a parent, the parent data isn't available. We fail
// to find an answer in the cache and fall back to querying the VCS.
return false;
}
}
/* -( Cache Internals )---------------------------------------------------- */
/**
* Get the bucket key for a given commit ID.
*
* @param int Commit ID.
* @return int Bucket key.
* @task cache
*/
private function getBucketKey($commit_id) {
return (int)floor($commit_id / $this->getBucketSize());
}
/**
* Get the cache key for a given bucket key (from @{method:getBucketKey}).
*
* @param int Bucket key.
* @return string Cache key.
* @task cache
*/
private function getBucketCacheKey($bucket_key) {
static $prefix;
if ($prefix === null) {
$self = get_class($this);
$size = $this->getBucketSize();
$prefix = "{$self}:{$size}:2:";
}
return $prefix.$bucket_key;
}
/**
* Get the number of items per bucket.
*
* @return int Number of items to store per bucket.
* @task cache
*/
private function getBucketSize() {
return 4096;
}
/**
* Retrieve or build a graph cache bucket from the cache.
*
* Normally, this operates as a readthrough cache call. It can also be used
* to force a cache update by passing the existing data to `$rebuild_data`.
*
* @param int Bucket key, from @{method:getBucketKey}.
* @param mixed Current data, to force a cache rebuild of this bucket.
* @return array Data from the cache.
* @task cache
*/
private function getBucketData($bucket_key, $rebuild_data = null) {
$cache_key = $this->getBucketCacheKey($bucket_key);
// TODO: This cache stuff could be handled more gracefully, but the
// database cache currently requires values to be strings and needs
// some tweaking to support this as part of a stack. Our cache semantics
// here are also unusual (not purely readthrough) because this cache is
// appendable.
$cache_level1 = PhabricatorCaches::getRepositoryGraphL1Cache();
$cache_level2 = PhabricatorCaches::getRepositoryGraphL2Cache();
if ($rebuild_data === null) {
$bucket_data = $cache_level1->getKey($cache_key);
if ($bucket_data) {
return $bucket_data;
}
$bucket_data = $cache_level2->getKey($cache_key);
if ($bucket_data) {
$unserialized = @unserialize($bucket_data);
if ($unserialized) {
// Fill APC if we got a database hit but missed in APC.
$cache_level1->setKey($cache_key, $unserialized);
return $unserialized;
}
}
}
if (!is_array($rebuild_data)) {
$rebuild_data = array();
}
$bucket_data = $this->rebuildBucket($bucket_key, $rebuild_data);
// Don't bother writing the data if we didn't update anything.
if ($bucket_data !== $rebuild_data) {
$cache_level2->setKey($cache_key, serialize($bucket_data));
$cache_level1->setKey($cache_key, $bucket_data);
}
return $bucket_data;
}
/**
- * Rebuild a cache bucket, amending existing data if avialable.
+ * Rebuild a cache bucket, amending existing data if available.
*
* @param int Bucket key, from @{method:getBucketKey}.
* @param array Existing bucket data.
* @return array Rebuilt bucket data.
* @task cache
*/
private function rebuildBucket($bucket_key, array $current_data) {
// First, check if we've already rebuilt this bucket. In some cases (like
// browsing a repository at some commit) it's common to issue many lookups
// against one commit. If that commit has been discovered but not yet
// fully imported, we'll repeatedly attempt to rebuild the bucket. If the
// first rebuild did not work, subsequent rebuilds are very unlikely to
// have any effect. We can just skip the rebuild in these cases.
if (isset($this->rebuiltKeys[$bucket_key])) {
return $current_data;
} else {
$this->rebuiltKeys[$bucket_key] = true;
}
$bucket_min = ($bucket_key * $this->getBucketSize());
$bucket_max = ($bucket_min + $this->getBucketSize()) - 1;
// We need to reload all of the commits in the bucket because there is
// no guarantee that they'll get parsed in order, so we can fill large
// commit IDs before small ones. Later on, we'll ignore the commits we
// already know about.
$table_commit = new PhabricatorRepositoryCommit();
$table_repository = new PhabricatorRepository();
$conn_r = $table_commit->establishConnection('r');
// Find all the Git and Mercurial commits in the block which have completed
// change import. We can't fill the cache accurately for commits which have
// not completed change import, so just pretend we don't know about them.
// In these cases, we will will ultimately fall back to VCS queries.
$commit_rows = queryfx_all(
$conn_r,
'SELECT c.id FROM %T c
JOIN %T r ON c.repositoryID = r.id AND r.versionControlSystem IN (%Ls)
WHERE c.id BETWEEN %d AND %d
AND (c.importStatus & %d) = %d',
$table_commit->getTableName(),
$table_repository->getTableName(),
array(
PhabricatorRepositoryType::REPOSITORY_TYPE_GIT,
PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL,
),
$bucket_min,
$bucket_max,
PhabricatorRepositoryCommit::IMPORTED_CHANGE,
PhabricatorRepositoryCommit::IMPORTED_CHANGE);
// If we don't have any data, just return the existing data.
if (!$commit_rows) {
return $current_data;
}
// Remove the commits we already have data for. We don't need to rebuild
// these. If there's nothing left, return the existing data.
$commit_ids = ipull($commit_rows, 'id', 'id');
$commit_ids = array_diff_key($commit_ids, $current_data);
if (!$commit_ids) {
return $current_data;
}
// Find all the path changes for the new commits.
$path_changes = queryfx_all(
$conn_r,
'SELECT commitID, pathID FROM %T
WHERE commitID IN (%Ld)
AND (isDirect = 1 OR changeType = %d)',
PhabricatorRepository::TABLE_PATHCHANGE,
$commit_ids,
DifferentialChangeType::TYPE_CHILD);
$path_changes = igroup($path_changes, 'commitID');
// Find all the parents for the new commits.
$parents = queryfx_all(
$conn_r,
'SELECT childCommitID, parentCommitID FROM %T
WHERE childCommitID IN (%Ld)
ORDER BY id ASC',
PhabricatorRepository::TABLE_PARENTS,
$commit_ids);
$parents = igroup($parents, 'childCommitID');
// Build the actual data for the cache.
foreach ($commit_ids as $commit_id) {
$parent_ids = array();
if (!empty($parents[$commit_id])) {
foreach ($parents[$commit_id] as $row) {
$parent_ids[] = (int)$row['parentCommitID'];
}
} else {
// We expect all rows to have parents (commits with no parents get
// an explicit "0" placeholder). If we're in an older repository, the
// parent information might not have been populated yet. Decline to fill
// the cache if we don't have the parent information, since the fill
// will be incorrect.
continue;
}
if (isset($path_changes[$commit_id])) {
$path_ids = $path_changes[$commit_id];
foreach ($path_ids as $key => $path_id) {
$path_ids[$key] = (int)$path_id['pathID'];
}
sort($path_ids);
} else {
$path_ids = array();
}
$value = $parent_ids;
$value[] = null;
foreach ($path_ids as $path_id) {
$value[] = $path_id;
}
$current_data[$commit_id] = $value;
}
return $current_data;
}
}
diff --git a/src/docs/contributor/database.diviner b/src/docs/contributor/database.diviner
index 273f1d273b..99ee5aeb68 100644
--- a/src/docs/contributor/database.diviner
+++ b/src/docs/contributor/database.diviner
@@ -1,175 +1,175 @@
@title Database Schema
@group developer
This document describes key components of the database schema and should answer
questions like how to store new types of data.
= Database System =
Phabricator uses MySQL with InnoDB engine. The only exception is the
`search_documentfield` table which uses MyISAM because MySQL doesn't support
fulltext search in InnoDB.
Let us know if you need to use other database system: @{article:Give Feedback!
Get Support!}.
= PHP Drivers =
Phabricator supports [[ http://www.php.net/book.mysql | MySQL ]] and
[[ http://www.php.net/book.mysqli | MySQLi ]] PHP extensions. Most installations
use MySQL but MySQLi should work equally well.
= Databases =
Each Phabricator application has its own database. The names are prefixed by
`phabricator_`. This design has two advantages:
* Each database is easier to comprehend and to maintain.
* We don't do cross-database joins so each database can live on its own machine
which is useful for load-balancing.
= Connections =
Phabricator specifies if it will use any opened connection just for reading or
also for writing. This allows opening write connections to master and read
connections to slave in master/slave replication. It is useful for
load-balancing.
= Tables =
Each table name is prefixed by its application. For example, Differential
revisions are stored in database `phabricator_differential` and table
`differential_revision`. This duplicity allows easy recognition of the table in
DarkConsole (see @{article:Using DarkConsole}) and other places.
The exception is tables which share the same schema over different databases
such as `edge`.
We use lower-case table names with words separated by underscores. The reason is
that MySQL can be configured (with `lower_case_table_names`) to lower-case the
table names anyway.
= Column Names =
Phabricator uses camelCase names for columns. The main advantage is that they
directly map to properties in PHP classes.
Don't use MySQL reserved words (such as `order`) for column names.
= Data Types =
Phabricator uses `int unsigned` columns for storing dates instead of `date` or
`datetime`. We don't need to care about time-zones in both MySQL and PHP because
of it. The other reason is that PHP internally uses numbers for storing dates.
Phabricator uses UTF-8 encoding for storing all text data. We use
`utf8_general_ci` collation for free-text and `utf8_bin` for identifiers.
We don't use the `enum` data type because each change to the list of possible
values requires altering the table (which is slow with big tables). We use
numbers (or short strings in some cases) mapped to PHP constants instead.
= JSON =
Some data don't require structured access - you don't need to filter or order by
them. We store these data as text fields in JSON format. This approach has
several advantages:
* If we decide to add another unstructured field then we don't need to alter the
table (which is slow for big tables in MySQL).
* Table structure is not cluttered by fields which could be unused most of the
time.
An example of such usage can be found in column
`differential_diffproperty.data`.
= Primary Keys =
Most tables have auto-increment column named `id`. However creating such column
is not required for tables which are not usually directly referenced (such as
tables expressing M:N relations). Example of such table is
`differential_relationship`.
= Indexes =
Create all indexes necessary for fast query execution in most cases. Don't
create indexes which are not used. You can analyze queries @{article:Using
DarkConsole}.
Older MySQL versions are not able to use indexes for tuple search:
`(a, b) IN ((%s, %d), (%s, %d))`. Use `AND` and `OR` instead:
`((a = %s AND b = %d) OR (a = %s AND b = %d))`.
= Foreign Keys =
We don't use InnoDB's foreign keys because our application is so great that
no inconsistencies can arise. It will just slow us down.
= PHIDs =
Each globally referencable object in Phabricator has its associated PHID
(Phabricator ID) which serves as a global identifier. We use PHIDs for
referencing data in different databases.
We use both autoincrementing IDs and global PHIDs because each is useful in
different contexts. Autoincrementing IDs are chronologically ordered and allow
us to construct short, human-readable object names (like D2258) and URIs. Global
PHIDs allow us to represent relationships between different types of objects in
-a homogenous way.
+a homogeneous way.
For example, the concept of "subscribers" is more powerfully done with PHIDs
because we could theoretically have users, projects, teams, and more all as
"subscribers" of other objects. Using an ID column we would need to add a
"type" column to avoid ID collision; using PHIDs does not require this
additional column.
= Transactions =
Transactional code should be written using transactions. Example of such code is
inserting multiple records where one doesn't make sense without the other or
selecting data later used for update. See chapter in @{class:LiskDAO}.
= Advanced Features =
We don't use MySQL advanced features such as triggers, stored procedures or
events because we like expressing the application logic in PHP more than in SQL.
Some of these features (especially triggers) can also cause big confusion.
Avoiding these advanced features is also good for supporting other database
systems (which we don't support anyway).
= Schema Denormalization =
Phabricator uses schema denormalization for performance reasons sparingly. Try
to avoid it if possible.
= Changing the Schema =
There are three simple steps to update the schema:
# Create a `.sql` file in `resources/sql/patches/`. This file should:
- - Contain the approprate MySQL commands to update the schema.
+ - Contain the appropriate MySQL commands to update the schema.
- Be named as `YYYYMMDD.patchname.ext`. For example, `20130217.example.sql`.
- Use `${NAMESPACE}` rather than `phabricator` for database names.
- Use `COLLATE utf8_bin` for any columns that are to be used as identifiers,
such as PHID columns. Otherwise, use `COLLATE utf8_general_ci`.
- Name all indexes so it is possible to delete them later.
# Edit `src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php` and
add your patch to @{method@phabricator:PhabricatorBuiltinPatchList::getPatches}.
# Run `bin/storage upgrade`.
It is also possible to create more complex patches in PHP for data migration
(due to schema changes or otherwise.) However, the schema changes themselves
should be done in separate `.sql` files. Order can be guaranteed by editing
`src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php`
appropriately.
See the
[[https://secure.phabricator.com/rPb39175342dc5bee0c2246b05fa277e76a7e96ed3
| commit adding policy storage for Paste ]] for a reasonable example of the code
changes.
= See Also =
* @{class:LiskDAO}
* @{class:PhabricatorPHID}
diff --git a/src/docs/user/userguide/arcanist_lint_script_and_regex.diviner b/src/docs/user/userguide/arcanist_lint_script_and_regex.diviner
index 6fe5db2a2f..e8d021b1ac 100644
--- a/src/docs/user/userguide/arcanist_lint_script_and_regex.diviner
+++ b/src/docs/user/userguide/arcanist_lint_script_and_regex.diviner
@@ -1,153 +1,153 @@
@title Arcanist User Guide: Script and Regex Linter
@group userguide
Explains how to use the Script and Regex linter to invoke an existing
lint engine that is not integrated with Arcanist.
The Script and Regex linter is a simple glue linter which runs some
script on each path, and then uses a regex to parse lint messages from
the script's output. (This linter uses a script and a regex to
interpret the results of some real linter, it does not itself lint
both scripts and regexes).
Configure this linter by setting these keys in your configuration:
- `script-and-regex.script` Script command to run. This can be
the path to a linter script, but may also include flags or use shell
features (see below for examples).
- `script-and-regex.regex` The regex to process output with. This
regex uses named capturing groups (detailed below) to interpret output.
The script will be invoked from the project root, so you can specify a
relative path like `scripts/lint.sh` or an absolute path like
`/opt/lint/lint.sh`.
This linter is necessarily more limited in its capabilities than a normal
linter which can perform custom processing, but may be somewhat simpler to
configure.
== Script... ==
The script will be invoked once for each file that is to be linted, with
the file passed as the first argument. The file may begin with a "-"; ensure
your script will not interpret such files as flags (perhaps by ending your
script configuration with "--", if its argument parser supports that).
Note that when run via `arc diff`, the list of files to be linted includes
deleted files and files that were moved away by the change. The linter should
not assume the path it is given exists, and it is not an error for the
linter to be invoked with paths which are no longer there. (Every affected
path is subject to lint because some linters may raise errors in other files
when a file is removed, or raise an error about its removal.)
The script should emit lint messages to stdout, which will be parsed with
the provided regex.
For example, you might use a configuration like this:
"script-and-regex.script": "/opt/lint/lint.sh --flag value --other-flag --"
stderr is ignored. If you have a script which writes messages to stderr,
you can redirect stderr to stdout by using a configuration like this:
"script-and-regex.script": "sh -c '/opt/lint/lint.sh \"$0\" 2>&1'"
The return code of the script must be 0, or an exception will be raised
reporting that the linter failed. If you have a script which exits nonzero
under normal circumstances, you can force it to always exit 0 by using a
configuration like this:
"script-and-regex.script": "sh -c '/opt/lint/lint.sh \"$0\" || true'"
Multiple instances of the script will be run in parallel if there are
multiple files to be linted, so they should not use any unique resources.
For instance, this configuration would not work properly, because several
processes may attempt to write to the file at the same time:
COUNTEREXAMPLE
"script-and-regex.script": "sh -c '/opt/lint/lint.sh --output /tmp/lint.out \"$0\" && cat /tmp/lint.out'"
There are necessary limits to how gracefully this linter can deal with
edge cases, because it is just a script and a regex. If you need to do
things that this linter can't handle, you can write a phutil linter and move
the logic to handle those cases into PHP. PHP is a better general-purpose
programming language than regular expressions are, if only by a small margin.
== ...and Regex ==
The regex must be a valid PHP PCRE regex, including delimiters and flags.
The regex will be matched against the entire output of the script, so it
should generally be in this form if messages are one-per-line:
/^...$/m
The regex should capture these named patterns with `(?P<name>...)`:
- `message` (required) Text describing the lint message. For example,
"This is a syntax error.".
- `name` (optional) Text summarizing the lint message. For example,
"Syntax Error".
- `severity` (optional) The word "error", "warning", "autofix", "advice",
or "disabled", in any combination of upper and lower case. Instead, you
may match groups called `error`, `warning`, `advice`, `autofix`, or
`disabled`. These allow you to match output formats like "E123" and
"W123" to indicate errors and warnings, even though the word "error" is
not present in the output. If no severity capturing group is present,
messages are raised with "error" severity. If multiple severity capturing
groups are present, messages are raised with the highest captured
- serverity. Capturing groups like `error` supersede the `severity`
+ severity. Capturing groups like `error` supersede the `severity`
capturing group.
- `error` (optional) Match some nonempty substring to indicate that this
message has "error" severity.
- `warning` (optional) Match some nonempty substring to indicate that this
message has "warning" severity.
- `advice` (optional) Match some nonempty substring to indicate that this
message has "advice" severity.
- `autofix` (optional) Match some nonempty substring to indicate that this
message has "autofix" severity.
- `disabled` (optional) Match some nonempty substring to indicate that this
message has "disabled" severity.
- `file` (optional) The name of the file to raise the lint message in. If
not specified, defaults to the linted file. It is generally not necessary
to capture this unless the linter can raise messages in files other than
the one it is linting.
- `line` (optional) The line number of the message.
- `char` (optional) The character offset of the message.
- `offset` (optional) The byte offset of the message. If captured, this
supersedes `line` and `char`.
- `original` (optional) The text the message affects.
- `replacement` (optional) The text that the range captured by `original`
should be automatically replaced by to resolve the message.
- `code` (optional) A short error type identifier which can be used
elsewhere to configure handling of specific types of messages. For
example, "EXAMPLE1", "EXAMPLE2", etc., where each code identifies a
class of message like "syntax error", "missing whitespace", etc. This
allows configuration to later change the severity of all whitespace
messages, for example.
- `ignore` (optional) Match some nonempty substring to ignore the match.
You can use this if your linter sometimes emits text like "No lint
errors".
- `stop` (optional) Match some nonempty substring to stop processing input.
Remaining matches for this file will be discarded, but linting will
continue with other linters and other files.
- `halt` (optional) Match some nonempty substring to halt all linting of
this file by any linter. Linting will continue with other files.
- `throw` (optional) Match some nonempty substring to throw an error, which
will stop `arc` completely. You can use this to fail abruptly if you
encounter unexpected output. All processing will abort.
Numbered capturing groups are ignored.
For example, if your lint script's output looks like this:
error:13 Too many goats!
warning:22 Not enough boats.
...you could use this regex to parse it:
/^(?P<severity>warning|error):(?P<line>\d+) (?P<message>.*)$/m
The simplest valid regex for line-oriented output is something like this:
/^(?P<message>.*)$/m
diff --git a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
index 4e48c729c1..6046bff847 100644
--- a/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
+++ b/src/infrastructure/query/policy/PhabricatorPolicyAwareQuery.php
@@ -1,641 +1,641 @@
<?php
/**
* A @{class:PhabricatorQuery} which filters results according to visibility
* policies for the querying user. Broadly, this class allows you to implement
* a query that returns only objects the user is allowed to see.
*
* $results = id(new ExampleQuery())
* ->setViewer($user)
* ->withConstraint($example)
* ->execute();
*
* Normally, you should extend @{class:PhabricatorCursorPagedPolicyAwareQuery},
* not this class. @{class:PhabricatorCursorPagedPolicyAwareQuery} provides a
* more practical interface for building usable queries against most object
* types.
*
* NOTE: Although this class extends @{class:PhabricatorOffsetPagedQuery},
* offset paging with policy filtering is not efficient. All results must be
* loaded into the application and filtered here: skipping `N` rows via offset
* is an `O(N)` operation with a large constant. Prefer cursor-based paging
* with @{class:PhabricatorCursorPagedPolicyAwareQuery}, which can filter far
* more efficiently in MySQL.
*
* @task config Query Configuration
* @task exec Executing Queries
* @task policyimpl Policy Query Implementation
*/
abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery {
private $viewer;
private $raisePolicyExceptions;
private $parentQuery;
private $rawResultLimit;
private $capabilities;
private $workspace = array();
private $policyFilteredPHIDs = array();
private $canUseApplication;
/* -( Query Configuration )------------------------------------------------ */
/**
* Set the viewer who is executing the query. Results will be filtered
* according to the viewer's capabilities. You must set a viewer to execute
* a policy query.
*
* @param PhabricatorUser The viewing user.
* @return this
* @task config
*/
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
/**
* Get the query's viewer.
*
* @return PhabricatorUser The viewing user.
* @task config
*/
final public function getViewer() {
return $this->viewer;
}
/**
* Set the parent query of this query. This is useful for nested queries so
* that configuration like whether or not to raise policy exceptions is
* seamlessly passed along to child queries.
*
* @return this
* @task config
*/
final public function setParentQuery(PhabricatorPolicyAwareQuery $query) {
$this->parentQuery = $query;
return $this;
}
/**
* Get the parent query. See @{method:setParentQuery} for discussion.
*
* @return PhabricatorPolicyAwareQuery The parent query.
* @task config
*/
final public function getParentQuery() {
return $this->parentQuery;
}
/**
* Hook to configure whether this query should raise policy exceptions.
*
* @return this
* @task config
*/
final public function setRaisePolicyExceptions($bool) {
$this->raisePolicyExceptions = $bool;
return $this;
}
/**
* @return bool
* @task config
*/
final public function shouldRaisePolicyExceptions() {
return (bool)$this->raisePolicyExceptions;
}
/**
* @task config
*/
final public function requireCapabilities(array $capabilities) {
$this->capabilities = $capabilities;
return $this;
}
/* -( Query Execution )---------------------------------------------------- */
/**
* Execute the query, expecting a single result. This method simplifies
* loading objects for detail pages or edit views.
*
* // Load one result by ID.
* $obj = id(new ExampleQuery())
* ->setViewer($user)
* ->withIDs(array($id))
* ->executeOne();
* if (!$obj) {
* return new Aphront404Response();
* }
*
* If zero results match the query, this method returns `null`.
* If one result matches the query, this method returns that result.
*
* If two or more results match the query, this method throws an exception.
* You should use this method only when the query constraints guarantee at
* most one match (e.g., selecting a specific ID or PHID).
*
* If one result matches the query but it is caught by the policy filter (for
* example, the user is trying to view or edit an object which exists but
* which they do not have permission to see) a policy exception is thrown.
*
* @return mixed Single result, or null.
* @task exec
*/
final public function executeOne() {
$this->setRaisePolicyExceptions(true);
try {
$results = $this->execute();
} catch (Exception $ex) {
$this->setRaisePolicyExceptions(false);
throw $ex;
}
if (count($results) > 1) {
throw new Exception('Expected a single result!');
}
if (!$results) {
return null;
}
return head($results);
}
/**
* Execute the query, loading all visible results.
*
* @return list<PhabricatorPolicyInterface> Result objects.
* @task exec
*/
final public function execute() {
if (!$this->viewer) {
throw new Exception('Call setViewer() before execute()!');
}
$parent_query = $this->getParentQuery();
if ($parent_query) {
$this->setRaisePolicyExceptions(
$parent_query->shouldRaisePolicyExceptions());
}
$results = array();
$filter = $this->getPolicyFilter();
$offset = (int)$this->getOffset();
$limit = (int)$this->getLimit();
$count = 0;
if ($limit) {
$need = $offset + $limit;
} else {
$need = 0;
}
$this->willExecute();
do {
if ($need) {
$this->rawResultLimit = min($need - $count, 1024);
} else {
$this->rawResultLimit = 0;
}
if ($this->canViewerUseQueryApplication()) {
try {
$page = $this->loadPage();
} catch (PhabricatorEmptyQueryException $ex) {
$page = array();
}
} else {
$page = array();
}
if ($page) {
$maybe_visible = $this->willFilterPage($page);
} else {
$maybe_visible = array();
}
if ($this->shouldDisablePolicyFiltering()) {
$visible = $maybe_visible;
} else {
$visible = $filter->apply($maybe_visible);
$policy_filtered = array();
foreach ($maybe_visible as $key => $object) {
if (empty($visible[$key])) {
$phid = $object->getPHID();
if ($phid) {
$policy_filtered[$phid] = $phid;
}
}
}
$this->addPolicyFilteredPHIDs($policy_filtered);
}
if ($visible) {
$this->putObjectsInWorkspace($this->getWorkspaceMapForPage($visible));
$visible = $this->didFilterPage($visible);
}
$removed = array();
foreach ($maybe_visible as $key => $object) {
if (empty($visible[$key])) {
$removed[$key] = $object;
}
}
$this->didFilterResults($removed);
foreach ($visible as $key => $result) {
++$count;
// If we have an offset, we just ignore that many results and start
// storing them only once we've hit the offset. This reduces memory
// requirements for large offsets, compared to storing them all and
// slicing them away later.
if ($count > $offset) {
$results[$key] = $result;
}
if ($need && ($count >= $need)) {
// If we have all the rows we need, break out of the paging query.
break 2;
}
}
if (!$this->rawResultLimit) {
// If we don't have a load count, we loaded all the results. We do
// not need to load another page.
break;
}
if (count($page) < $this->rawResultLimit) {
// If we have a load count but the unfiltered results contained fewer
// objects, we know this was the last page of objects; we do not need
// to load another page because we can deduce it would be empty.
break;
}
$this->nextPage($page);
} while (true);
$results = $this->didLoadResults($results);
return $results;
}
private function getPolicyFilter() {
$filter = new PhabricatorPolicyFilter();
$filter->setViewer($this->viewer);
$capabilities = $this->getRequiredCapabilities();
$filter->requireCapabilities($capabilities);
$filter->raisePolicyExceptions($this->shouldRaisePolicyExceptions());
return $filter;
}
protected function getRequiredCapabilities() {
if ($this->capabilities) {
return $this->capabilities;
}
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
protected function applyPolicyFilter(array $objects, array $capabilities) {
if ($this->shouldDisablePolicyFiltering()) {
return $objects;
}
$filter = $this->getPolicyFilter();
$filter->requireCapabilities($capabilities);
return $filter->apply($objects);
}
protected function didRejectResult(PhabricatorPolicyInterface $object) {
$this->getPolicyFilter()->rejectObject(
$object,
$object->getPolicy(PhabricatorPolicyCapability::CAN_VIEW),
PhabricatorPolicyCapability::CAN_VIEW);
}
public function addPolicyFilteredPHIDs(array $phids) {
$this->policyFilteredPHIDs += $phids;
if ($this->getParentQuery()) {
$this->getParentQuery()->addPolicyFilteredPHIDs($phids);
}
return $this;
}
/**
* Return a map of all object PHIDs which were loaded in the query but
* filtered out by policy constraints. This allows a caller to distinguish
* between objects which do not exist (or, at least, were filtered at the
* content level) and objects which exist but aren't visible.
*
* @return map<phid, phid> Map of object PHIDs which were filtered
* by policies.
* @task exec
*/
public function getPolicyFilteredPHIDs() {
return $this->policyFilteredPHIDs;
}
/* -( Query Workspace )---------------------------------------------------- */
/**
* Put a map of objects into the query workspace. Many queries perform
* subqueries, which can eventually end up loading the same objects more than
* once (often to perform policy checks).
*
* For example, loading a user may load the user's profile image, which might
* load the user object again in order to verify that the viewer has
* permission to see the file.
*
* The "query workspace" allows queries to load objects from elsewhere in a
* query block instead of refetching them.
*
* When using the query workspace, it's important to obey two rules:
*
* **Never put objects into the workspace which the viewer may not be able
* to see**. You need to apply all policy filtering //before// putting
* objects in the workspace. Otherwise, subqueries may read the objects and
* use them to permit access to content the user shouldn't be able to view.
*
* **Fully enrich objects pulled from the workspace.** After pulling objects
* from the workspace, you still need to load and attach any additional
* content the query requests. Otherwise, a query might return objects without
* requested content.
*
* Generally, you do not need to update the workspace yourself: it is
* automatically populated as a side effect of objects surviving policy
* filtering.
*
* @param map<phid, PhabricatorPolicyInterface> Objects to add to the query
* workspace.
* @return this
* @task workspace
*/
public function putObjectsInWorkspace(array $objects) {
assert_instances_of($objects, 'PhabricatorPolicyInterface');
$viewer_phid = $this->getViewer()->getPHID();
// The workspace is scoped per viewer to prevent accidental contamination.
if (empty($this->workspace[$viewer_phid])) {
$this->workspace[$viewer_phid] = array();
}
$this->workspace[$viewer_phid] += $objects;
return $this;
}
/**
* Retrieve objects from the query workspace. For more discussion about the
* workspace mechanism, see @{method:putObjectsInWorkspace}. This method
* searches both the current query's workspace and the workspaces of parent
* queries.
*
- * @param list<phid> List of PHIDs to retreive.
+ * @param list<phid> List of PHIDs to retrieve.
* @return this
* @task workspace
*/
public function getObjectsFromWorkspace(array $phids) {
$viewer_phid = $this->getViewer()->getPHID();
$results = array();
foreach ($phids as $key => $phid) {
if (isset($this->workspace[$viewer_phid][$phid])) {
$results[$phid] = $this->workspace[$viewer_phid][$phid];
unset($phids[$key]);
}
}
if ($phids && $this->getParentQuery()) {
$results += $this->getParentQuery()->getObjectsFromWorkspace($phids);
}
return $results;
}
/**
* Convert a result page to a `<phid, PhabricatorPolicyInterface>` map.
*
* @param list<PhabricatorPolicyInterface> Objects.
* @return map<phid, PhabricatorPolicyInterface> Map of objects which can
* be put into the workspace.
* @task workspace
*/
protected function getWorkspaceMapForPage(array $results) {
$map = array();
foreach ($results as $result) {
$phid = $result->getPHID();
if ($phid !== null) {
$map[$phid] = $result;
}
}
return $map;
}
/* -( Policy Query Implementation )---------------------------------------- */
/**
* Get the number of results @{method:loadPage} should load. If the value is
* 0, @{method:loadPage} should load all available results.
*
* @return int The number of results to load, or 0 for all results.
* @task policyimpl
*/
final protected function getRawResultLimit() {
return $this->rawResultLimit;
}
/**
* Hook invoked before query execution. Generally, implementations should
* reset any internal cursors.
*
* @return void
* @task policyimpl
*/
protected function willExecute() {
return;
}
/**
* Load a raw page of results. Generally, implementations should load objects
* from the database. They should attempt to return the number of results
* hinted by @{method:getRawResultLimit}.
*
* @return list<PhabricatorPolicyInterface> List of filterable policy objects.
* @task policyimpl
*/
abstract protected function loadPage();
/**
* Update internal state so that the next call to @{method:loadPage} will
* return new results. Generally, you should adjust a cursor position based
* on the provided result page.
*
* @param list<PhabricatorPolicyInterface> The current page of results.
* @return void
* @task policyimpl
*/
abstract protected function nextPage(array $page);
/**
* Hook for applying a page filter prior to the privacy filter. This allows
* you to drop some items from the result set without creating problems with
* pagination or cursor updates. You can also load and attach data which is
* required to perform policy filtering.
*
* Generally, you should load non-policy data and perform non-policy filtering
* later, in @{method:didFilterPage}. Strictly fewer objects will make it that
* far (so the program will load less data) and subqueries from that context
* can use the query workspace to further reduce query load.
*
* This method will only be called if data is available. Implementations
* do not need to handle the case of no results specially.
*
* @param list<wild> Results from `loadPage()`.
* @return list<PhabricatorPolicyInterface> Objects for policy filtering.
* @task policyimpl
*/
protected function willFilterPage(array $page) {
return $page;
}
/**
* Hook for performing additional non-policy loading or filtering after an
* object has satisfied all policy checks. Generally, this means loading and
* attaching related data.
*
* Subqueries executed during this phase can use the query workspace, which
* may improve performance or make circular policies resolvable. Data which
* is not necessary for policy filtering should generally be loaded here.
*
* This callback can still filter objects (for example, if attachable data
* is discovered to not exist), but should not do so for policy reasons.
*
* This method will only be called if data is available. Implementations do
* not need to handle the case of no results specially.
*
* @param list<wild> Results from @{method:willFilterPage()}.
* @return list<PhabricatorPolicyInterface> Objects after additional
* non-policy processing.
*/
protected function didFilterPage(array $page) {
return $page;
}
/**
* Hook for removing filtered results from alternate result sets. This
* hook will be called with any objects which were returned by the query but
* filtered for policy reasons. The query should remove them from any cached
* or partial result sets.
*
* @param list<wild> List of objects that should not be returned by alternate
* result mechanisms.
* @return void
* @task policyimpl
*/
protected function didFilterResults(array $results) {
return;
}
/**
* Hook for applying final adjustments before results are returned. This is
* used by @{class:PhabricatorCursorPagedPolicyAwareQuery} to reverse results
* that are queried during reverse paging.
*
* @param list<PhabricatorPolicyInterface> Query results.
* @return list<PhabricatorPolicyInterface> Final results.
* @task policyimpl
*/
protected function didLoadResults(array $results) {
return $results;
}
/**
* Allows a subclass to disable policy filtering. This method is dangerous.
* It should be used only if the query loads data which has already been
* filtered (for example, because it wraps some other query which uses
* normal policy filtering).
*
* @return bool True to disable all policy filtering.
* @task policyimpl
*/
protected function shouldDisablePolicyFiltering() {
return false;
}
/**
* If this query belongs to an application, return the application class name
* here. This will prevent the query from returning results if the viewer can
* not access the application.
*
* If this query does not belong to an application, return `null`.
*
* @return string|null Application class name.
*/
abstract public function getQueryApplicationClass();
/**
* Determine if the viewer has permission to use this query's application.
* For queries which aren't part of an application, this method always returns
* true.
*
* @return bool True if the viewer has application-level permission to
* execute the query.
*/
public function canViewerUseQueryApplication() {
if ($this->canUseApplication === null) {
$class = $this->getQueryApplicationClass();
if (!$class) {
$this->canUseApplication = true;
} else {
$result = id(new PhabricatorApplicationQuery())
->setViewer($this->getViewer())
->withClasses(array($class))
->execute();
$this->canUseApplication = (bool)$result;
}
}
return $this->canUseApplication;
}
}
diff --git a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js
index 909a994e0c..3ce355a2e0 100644
--- a/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js
+++ b/webroot/rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js
@@ -1,371 +1,371 @@
/**
* @requires javelin-install
* javelin-util
* javelin-dom
* javelin-typeahead-normalizer
* @provides javelin-typeahead-source
* @javelin
*/
JX.install('TypeaheadSource', {
construct : function() {
this._raw = {};
this._lookup = {};
this.setNormalizer(JX.TypeaheadNormalizer.normalize);
this._excludeIDs = {};
},
events : ['waiting', 'resultsready', 'complete'],
properties : {
/**
* Allows you to specify a function which will be used to normalize strings.
* Strings are normalized before being tokenized, and before being sent to
* the server. The purpose of normalization is to strip out irrelevant data,
* like uppercase/lowercase, extra spaces, or punctuation. By default,
* the @{JX.TypeaheadNormalizer} is used to normalize strings, but you may
- * want to provide a different normalizer, particiularly if there are
+ * want to provide a different normalizer, particularly if there are
* special characters with semantic meaning in your object names.
*
* @param function
*/
normalizer : null,
/**
* If a typeahead query should be processed before being normalized and
* tokenized, specify a queryExtractor.
*
* @param function
*/
queryExtractor : null,
/**
* Transformers convert data from a wire format to a runtime format. The
* transformation mechanism allows you to choose an efficient wire format
* and then expand it on the client side, rather than duplicating data
* over the wire. The transformation is applied to objects passed to
* addResult(). It should accept whatever sort of object you ship over the
* wire, and produce a dictionary with these keys:
*
* - **id**: a unique id for each object.
* - **name**: the string used for matching against user input.
* - **uri**: the URI corresponding with the object (must be present
* but need not be meaningful)
*
* You can also give:
* - **display**: the text or nodes to show in the DOM. Usually just the
* same as ##name##.
* - **tokenizable**: if you want to tokenize something other than the
* ##name##, for the typeahead to complete on, specify it here. A
* selected entry from the typeahead will still insert the ##name##
* into the input, but the ##tokenizable## field lets you complete on
* non-name things.
*
* The default transformer expects a three element list with elements
* [name, uri, id]. It assigns the first element to both ##name## and
* ##display##.
*
* @param function
*/
transformer : null,
/**
* Configures the maximum number of suggestions shown in the typeahead
* dropdown.
*
* @param int
*/
maximumResultCount : 5,
/**
* Optional function which is used to sort results. Inputs are the input
* string, the list of matches, and a default comparator. The function
* should sort the list for display. This is the minimum useful
* implementation:
*
* function(value, list, comparator) {
* list.sort(comparator);
* }
*
* Alternatively, you may pursue more creative implementations.
*
* The `value` is a raw string; you can bind the datasource into the
* function and use normalize() or tokenize() to parse it.
*
* The `list` is a list of objects returned from the transformer function,
* see the `transformer` property. These are the objects in the list which
* match the value.
*
* The `comparator` is a sort callback which implements sensible default
* sorting rules (e.g., alphabetic order), which you can use as a fallback
* if you just want to tweak the results (e.g., put some items at the top).
*
* The function is called after the user types some text, immediately before
* the possible completion results are displayed to the user.
*
* @param function
*/
sortHandler : null,
/**
* Optional function which is used to filter results before display. Inputs
* are the input string and a list of matches. The function should
* return a list of matches to display. This is the minimum useful
* implementation:
*
* function(value, list) {
* return list;
* }
*
* @param function
*/
filterHandler : null
},
members : {
_raw : null,
_lookup : null,
_excludeIDs : null,
_changeListener : null,
_startListener : null,
bindToTypeahead : function(typeahead) {
this._changeListener = typeahead.listen(
'change',
JX.bind(this, this.didChange)
);
this._startListener = typeahead.listen(
'start',
JX.bind(this, this.didStart)
);
},
unbindFromTypeahead : function() {
this._changeListener.remove();
this._startListener.remove();
},
didChange : function(value) {
return;
},
didStart : function() {
return;
},
clearCache : function() {
this._raw = {};
this._lookup = {};
},
addExcludeID : function(id) {
if (id) {
this._excludeIDs[id] = true;
}
},
removeExcludeID : function (id) {
if (id) {
delete this._excludeIDs[id];
}
},
addResult : function(obj) {
obj = (this.getTransformer() || this._defaultTransformer)(obj);
if (obj.id in this._raw) {
// We're already aware of this result. This will happen if someone
// searches for "zeb" and then for "zebra" with a
// TypeaheadRequestSource, for example, or the datasource just doesn't
// dedupe things properly. Whatever the case, just ignore it.
return;
}
if (__DEV__) {
for (var k in {name : 1, id : 1, display : 1, uri : 1}) {
if (!(k in obj)) {
throw new Error(
"JX.TypeaheadSource.addResult(): " +
"result must have properties 'name', 'id', 'uri' and 'display'.");
}
}
}
this._raw[obj.id] = obj;
var t = this.tokenize(obj.tokenizable || obj.name);
for (var jj = 0; jj < t.length; ++jj) {
if (!this._lookup.hasOwnProperty(t[jj])) {
this._lookup[t[jj]] = [];
}
this._lookup[t[jj]].push(obj.id);
}
},
waitForResults : function() {
this.invoke('waiting');
return this;
},
/**
* Get the raw state of a result by its ID. A number of other events and
* mechanisms give a list of result IDs and limited additional data; if you
* need to act on the full result data you can look it up here.
*
* @param scalar Result ID.
* @return dict Corresponding raw result.
*/
getResult : function(id) {
return this._raw[id];
},
matchResults : function(value, partial) {
// This table keeps track of the number of tokens each potential match
// has actually matched. When we're done, the real matches are those
// which have matched every token (so the value is equal to the token
// list length).
var match_count = {};
// This keeps track of distinct matches. If the user searches for
// something like "Chris C" against "Chris Cox", the "C" will match
// both fragments. We need to make sure we only count distinct matches.
var match_fragments = {};
var matched = {};
var seen = {};
var query_extractor = this.getQueryExtractor();
if (query_extractor) {
value = query_extractor(value);
}
var t = this.tokenize(value);
// Sort tokens by longest-first. We match each name fragment with at
// most one token.
t.sort(function(u, v) { return v.length - u.length; });
for (var ii = 0; ii < t.length; ++ii) {
// Do something reasonable if the user types the same token twice; this
// is sort of stupid so maybe kill it?
if (t[ii] in seen) {
t.splice(ii--, 1);
continue;
}
seen[t[ii]] = true;
var fragment = t[ii];
for (var name_fragment in this._lookup) {
if (name_fragment.substr(0, fragment.length) === fragment) {
if (!(name_fragment in matched)) {
matched[name_fragment] = true;
} else {
continue;
}
var l = this._lookup[name_fragment];
for (var jj = 0; jj < l.length; ++jj) {
var match_id = l[jj];
if (!match_fragments[match_id]) {
match_fragments[match_id] = {};
}
if (!(fragment in match_fragments[match_id])) {
match_fragments[match_id][fragment] = true;
match_count[match_id] = (match_count[match_id] || 0) + 1;
}
}
}
}
}
var hits = [];
for (var k in match_count) {
if (match_count[k] == t.length && !this._excludeIDs[k]) {
hits.push(k);
}
}
this.filterAndSortHits(value, hits);
var nodes = this.renderNodes(value, hits);
this.invoke('resultsready', nodes, value);
if (!partial) {
this.invoke('complete');
}
},
filterAndSortHits : function(value, hits) {
var objs = [];
var ii;
for (ii = 0; ii < hits.length; ii++) {
objs.push(this._raw[hits[ii]]);
}
var default_comparator = function(u, v) {
var key_u = u.sort || u.name;
var key_v = v.sort || v.name;
return key_u.localeCompare(key_v);
};
var filter_handler = this.getFilterHandler() || function(value, list) {
return list;
};
objs = filter_handler(value, objs);
var sort_handler = this.getSortHandler() || function(value, list, cmp) {
list.sort(cmp);
};
sort_handler(value, objs, default_comparator);
hits.splice(0, hits.length);
for (ii = 0; ii < objs.length; ii++) {
hits.push(objs[ii].id);
}
},
renderNodes : function(value, hits) {
var n = Math.min(this.getMaximumResultCount(), hits.length);
var nodes = [];
for (var kk = 0; kk < n; kk++) {
nodes.push(this.createNode(this._raw[hits[kk]]));
}
return nodes;
},
createNode : function(data) {
return JX.$N(
'a',
{
sigil: 'typeahead-result',
href: data.uri,
name: data.name,
rel: data.id,
className: 'jx-result'
},
data.display
);
},
normalize : function(str) {
return this.getNormalizer()(str);
},
tokenize : function(str) {
str = this.normalize(str);
if (!str.length) {
return [];
}
return str.split(/\s/g);
},
_defaultTransformer : function(object) {
return {
name : object[0],
display : object[0],
uri : object[1],
id : object[2]
};
}
}
});
diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js
index 0825032fa7..1600470a54 100644
--- a/webroot/rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js
+++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js
@@ -1,64 +1,64 @@
/**
* @provides javelin-behavior-dashboard-query-panel-select
* @requires javelin-behavior
* javelin-dom
*/
/**
* When editing a "Query" panel on dashboards, make the "Query" selector control
* dynamically update in response to changes to the "Engine" selector control.
*/
JX.behavior('dashboard-query-panel-select', function(config) {
var app_control = JX.$(config.applicationID);
var query_control = JX.$(config.queryID);
// If we have a currently-selected query, add it to the appropriate group
// in the options list if it does not already exist.
if (config.value.key !== null) {
var app = app_control.value;
if (!(app in config.options)) {
config.options[app] = [];
}
var found = false;
for (var ii = 0; ii < config.options[app].length; ii++) {
if (config.options[app][ii].key == config.value.key) {
found = true;
break;
}
}
if (!found) {
config.options[app] = [config.value].concat(config.options[app]);
}
}
// When the user changes the selected search engine, update the query
- // control to show avialable queries for that engine.
+ // control to show available queries for that engine.
function update() {
var app = app_control.value;
var old_value = query_control.value;
var new_value = null;
var options = config.options[app] || [];
var nodes = [];
for (var ii = 0; ii < options.length; ii++) {
if (new_value === null) {
new_value = options[ii].key;
}
if (options[ii].key == old_value) {
new_value = options[ii].key;
}
nodes.push(JX.$N('option', {value: options[ii].key}, options[ii].name));
}
JX.DOM.setContent(query_control, nodes);
query_control.value = new_value;
}
JX.DOM.listen(app_control, 'change', null, function() { update(); });
update();
});

File Metadata

Mime Type
text/x-diff
Expires
Mon, Jul 28, 11:16 PM (1 w, 4 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
187625
Default Alt Text
(65 KB)

Event Timeline